[Scummvm-git-logs] scummvm-tools master -> 12ca962b150f2eca76785827c361ede3cbbde29b

sev- sev at scummvm.org
Sun Dec 6 17:08:20 UTC 2020


This automated email contains information about 167 new commits which have been
pushed to the 'scummvm-tools' repo located at https://github.com/scummvm/scummvm-tools .

Summary:
28f00e58cd TOOLS: PETKA: Initial commit
f0008988a6 TOOLS: PETKA: Objects and scenes
9c043f222a TOOLS: PETKA: Fix from loading DEMO
8f3e8e5d64 TOOLS: PETKA: Canvas rendering, BMP loading
37c26c9520 TOOLS: PETKA: Resource list
7d64e0d2b5 TOOLS: PETKA: Select part, select resource
9405a96b3a TOOLS: PETKA: Fix unloading stores
b2342c929b TOOLS: PETKA: Switchable canvas and frame
acea4a19eb TOOLS: PETKA: Siwtchable interface works, outline mode
cdb9edbb36 TOOLS: PETKA: Info panel fixes (not yet finished)
42806a5366 TOOLS: PETKA: Switchable interface works
86a8ced5ac TOOLS: PETKA: Fix unloading stores
8f81a2b914 TOOLS: PETKA: Refine outline information
dd01092a27 TOOLS: PETKA: Refine outline information
f7dfc9c6f1 TOOLS: PETKA: Hyperlinks in info panel
8ed83d8ed4 TOOLS: PETKA: Info panel works, refactor
56e1eaa097 TOOLS: PETKA: Names and invntr information
0600e6948c TOOLS: PETKA: Names anr invntr order as in file
82c8104b14 TOOLS: PETKA: Open aliases and invntr from objects, refactor
b62ea77dc2 TOOLS: PETKA: Cleanup removed links
4d17a6243e TOOLS: PETKA: Cleanup removed links
715ed6ef51 TOOLS: PETKA: Resource filters
47a787d10e TOOLS: PETKA: Refactor, scene referenced objects
26b39b9aa3 TOOLS: PETKA: Refactor navigation
9b4d47482c TOOLS: PETKA: Refactor: objects, scenes, parts workss again
4b7bd0f016 TOOLS: PETKA: Refactor: names and invntr works
0c54fa10a2 TOOLS: PETKA: Refactor: names and invntr works
6cad232811 TOOLS: PETKA: Refactor: test image and info panel
b7adac7302 TOOLS: PETKA: Refactor: switch info and canvas
72bf691b39 TOOLS: PETKA: Refactor: resources works (filter and all list)
4f62901edd TOOLS: PETKA: Refactor image loading
11850e3b02 TOOLS: PETKA: Refactor image loading
c86264eca6 TOOLS: PETKA: Initial Pillow support
bfabee3932 TOOLS: PETKA: Pillow support (not finished)
b9c84877f9 TOOLS: PETKA: Reading BMP with PILLOW (baggy)
2603ab1f9b TOOLS: PETKA: Reading BMP with PILLOW (baggy)
cf98f30a46 TOOLS: PETKA: BMP can be loaded correctly most times
8afb6d50b0 TOOLS: PETKA: Select only one item in listbox, refine
e98266437b TOOLS: PETKA: REfactor: internal locations and text tagging
8c273ad397 TOOLS: PETKA: Information for bmp and same
c6045ebfa0 TOOLS: PETKA: fix
593103b907 TOOLS: PETKA: Fix switchinf resource filter
c508e1a902 TOOLS: PETKA: View FLC files (pallette not loaded)
73d0812642 TOOLS: PETKA: Messages works
f7fa014126 TOOLS: PETKA: Messages works
ee14e7f6c3 TOOLS: PETKA: Dialogue.fix groups (partial)
423e063c13 TOOLS: PETKA: dialogues works
d3f052e1bd TOOLS: PETKA: links from messages to dialoggroups
a1665895e4 TOOLS: PETKA: refactor
58398995a4 TOOLS: PETKA: refactor
ac443fbabf TOOLS: PETKA: links from dialog acts to objects
f6dc6ed300 TOOLS: PETKA: refactor
ace7622382 TOOLS: PETKA: more links to dialogs
720c836c5d TOOLS: PETKA: fix underline on win
b3e0b4b37b TOOLS: PETKA: History (only back)
0430f3a412 TOOLS: PETKA: History (only back)
0e69814055 TOOLS: PETKA: History fixed for not item selected
05a3a11403 TOOLS: PETKA: History fixed for not item selected
655ff79ed9 TOOLS: PETKA: History back and forward works
a7922be8fa TOOLS: PETKA: Clear history on part change
eec272940b TOOLS: PETKA: Open dialog, small fixes
d2c1894e9c TOOLS: PETKA: ref to object from action handler
b590021b4c TOOLS: PETKA: Dist file for cx_Freeze
726a9148be TOOLS: PETKA: release 0.2a (fix using resources in freeze mode)
71e35612b7 TOOLS: PETKA: release 0.2b (fix errors when no data loaded, add support information)
df73109607 TOOLS: PETKA: release 0.2c (fix support page)
ccbd3e23f8 TOOLS: PETKA: release 0.2c
706c7d5b1a TOOLS: PETKA: Fix gui, wat to fix path with incorrect loading
186095934c TOOLS: PETKA: Help pages
f3918517d0 TOOLS: PETKA: refactor internal paths
c2f1863df4 TOOLS: PETKA: refactor onjs and scenes
9b7ff2f6ff TOOLS: PETKA: refactor
92814b2799 TOOLS: PETKA: refactor
855c321e5a TOOLS: PETKA: Casts data
5faa366358 TOOLS: PETKA: Casts data
d8cc88b71b TOOLS: PETKA: refactor
82d97637d1 TOOLS: PETKA: refactor
e5adc359d0 TOOLS: PETKA: Context help
fc10e027f2 TOOLS: PETKA: Info, help pages
1bd7e2b3b9 TOOLS: PETKA: help pages
1f44482793 TOOLS: PETKA: fixes
5aa3199fde TOOLS: PETKA: Open data and select item from command line
d1f513f432 TOOLS: PETKA: fix dist
30075a2450 TOOLS: PETKA: Commant for dialog opcode 8
aa5eafc13a TOOLS: PETKA: code position for dialog opcodes
5e4bfe56ef TOOLS: PETKA: Improved help system
ce018979a3 TOOLS: PETKA: More dialog opcodes information in MENU
55c1dac47c TOOLS: PETKA: More dialog opcodes information in MENU
1caee4daec TOOLS: PETKA: More dialog opcodes for BREAK
86b69588ea TOOLS: PETKA: fix
45ef71d4bb TOOLS: PETKA: Decompile highlighting
adf0fe0f22 TOOLS: PETKA: Display cast colors
83c51d77a6 TOOLS: PETKA: Show where object used in TALK opcode
611c55928c TOOLS: PETKA: Show where object used in TALK opcode
3223278961 TOOLS: PETKA: Show where object used in all opcodes
c32cf831b5 TOOLS: PETKA: search opcodes refs in scenes too
5c26722f94 TOOLS: PETKA: test export .pot files
91b4caf9ce TOOLS: PETKA: Translation tools
81d2dbbea5 TOOLS: PETKA: add help file for translations
ece09a8a91 TOOLS: PETKA: Add license file
2ecdc95048 TOOLS: PETKA: refactor
adc0fdd3ae TOOLS: PETKA: Fix loading BGS.INI, add support info about start scene
641d9860b5 TOOLS: PETKA: Fix loading BGS.INI, add support info about start scene
c3fc6f0331 TOOLS: PETKA: Add enter areas information
c31c1f8f7d TOOLS: PETKA: transliterate templates
396efadba7 TOOLS: PETKA: release 0.2n; remove tranliterate library
01173a0031 TOOLS: PETKA: release 0.2n; remove tranliterate library
740c900e52 TOOLS: PETKA: release 0.2l
f0d14889aa TOOLS: PETKA: fix version
80f87766e1 TOOLS: PETKA: fix resource opening
73ead29a64 TOOLS: PETKA: Fix translit Capital cyrillic E
d81b65fe32 TOOLS: PETKA: Standalone decompiler for SCRIPT.DAT
668a706517 TOOLS: PETKA: Compiler for SCRIPT.DAT
c4a8a50350 TOOLS: PETKA: Decompiler for DIALOGUE.FIX and .LOD
68e2468825 TOOLS: PETKA: Compile dialog - dialogue.lod
1f1901479c TOOLS: PETKA: Compiler for dialogs
bd67cfbdba TOOLS: PETKA: fix doc
ce8130943d TOOLS: PETKA: Fixes
2e1f405a36 TOOLS: PETKA: Fixes
75743464db TOOLS: PETKA: license.txt отредактирован онлайн на Bitbucket
8aa21d6ce2 TOOLS: PETKA: Add opcodes and dialog opcodes statistic
736358e9a0 TOOLS: PETKA: Fix group display
a954806273 TOOLS: PETKA: Fix changes
b33be223a7 TOOLS: PETKA: Fix loading FLC with Pillow
aa0c112ff3 TOOLS: PETKA: changes
b380b5dfc8 TOOLS: PETKA: add stores info
f30896d019 TOOLS: PETKA: Store and file view works
97f1822092 TOOLS: PETKA: Display sorted wav list in messages section
aa4025e708 TOOLS: PETKA: Extract STR files
6c2a6061ae TOOLS: PETKA: testing page modes
0add1b8e92 TOOLS: PETKA: show history path
9128fae7fa TOOLS: PETKA: sort modes for messages
1e024eabb3 TOOLS: PETKA: add label to toolbar
d87373a2bf TOOLS: PETKA: files related info
98b6710ceb TOOLS: PETKA: fix bunch of error. improvements
f5c4aa3cdc TOOLS: PETKA: sort files, files in stores
65bbf7768e TOOLS: PETKA: fix long lists
2be7275bd5 TOOLS: PETKA: fix tab for opcodes
56a2a6b1c1 TOOLS: PETKA: decode MSK, LEG, OFF. display MSK, LEG, OFF, FLC
36514cf080 TOOLS: PETKA: fix doc
040c526b50 TOOLS: PETKA: fix doc, fix other
7c2bb9349c TOOLS: PETKA: enchance
08e06d907b TOOLS: PETKA: fixes
78fd00b49c TOOLS: PETKA: canv resize
56d009d9d5 TOOLS: PETKA: refactor bgs loading
17d4eba581 TOOLS: PETKA: save decoding
762ebd64ed TOOLS: PETKA: saved decoding: inventory
a820343b11 TOOLS: PETKA: saves decoding: charter positions, cursor resources
d8b4720443 TOOLS: PETKA: saves: struct decoded
34ad33b6b1 TOOLS: PETKA: saves display
b266ee00dd TOOLS: PETKA: fixes
f5a459b9ae TOOLS: PETKA: fix dislog opcodes info
7f09c7ed2a TOOLS: PETKA: saves almost decoded
21155abca7 TOOLS: PETKA: saves loading in engine (wip)
31cf5f8126 TOOLS: PETKA: fix compiler: creating scenes with zero references to objects
9f0f8c56ea TOOLS: PETKA: change save decoding
3219f4b115 TOOLS: PETKA: add interlinks for objetcs and dialog groups. allow open home sites
461afeebad TOOLS: PETKA: fix scenes with no referenced objects. add interlinks between objects and scenes
ba42c34aa5 TOOLS: PETKA: Refactor GUI to separate file
b6ef152bc3 TOOLS: PETKA: refactor gui
950acde83a TOOLS: PETKA: Fixes for gui
49cc81971b TOOLS: PETKA: linefeed do not break color tags
9f235c5d8e TOOLS: PETKA: refactor markup
66a62d9911 TOOLS: PETKA: replace html parser with HtmlParser
bfb4aa5058 TOOLS: PETKA: Add new dump function - for automatic testing generated data
0225608fd3 TOOLS: PETKA: Bring to modern python3
12ca962b15 JANITORIAL: Remove trailing spaces


Commit: 28f00e58cd27ee86e25da4ec0a959ee261895e3c
    https://github.com/scummvm/scummvm-tools/commit/28f00e58cd27ee86e25da4ec0a959ee261895e3c
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Initial commit

Changed paths:
  A engines/petka/p12explore.py
  A engines/petka/petka/__init__.py
  A engines/petka/petka/engine.py
  A engines/petka/petka/fman.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
new file mode 100755
index 000000000..9d7e1969e
--- /dev/null
+++ b/engines/petka/p12explore.py
@@ -0,0 +1,87 @@
+#! /usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# romiq.kh at gmail.com, 2014
+
+import sys, os
+import tkinter
+from tkinter import ttk
+
+import petka
+
+APPNAME = "P1&2 Explorer"
+
+class App(tkinter.Frame):
+    def __init__(self, master):
+        tkinter.Frame.__init__(self, master)
+        master.title(APPNAME)
+        self.pack(fill = tkinter.BOTH, expand = 1)
+        self.createWidgets()
+        self.createMenu()
+        
+        self.sim = None
+        
+    def createWidgets(self):
+        
+        ttk.Style().configure("Tool.TButton", width = -1) # minimal width
+        #ttk.Style().configure("TLabel", padding = PADDING)
+
+
+    def createMenu(self):
+        self.menubar = tkinter.Menu(self.master)
+        self.master.configure(menu = self.menubar)
+
+        self.menufile = tkinter.Menu(self.master, tearoff = 0)
+        self.menubar.add_cascade(menu = self.menufile,
+                label="File")
+        self.menufile.add_command(
+                command = self.on_open_data,
+                label="Open data...")
+        self.menufile.add_separator()
+        self.menufile.add_command(
+                command = self.on_exit,
+                label="Quit")    
+
+        self.menuedit = tkinter.Menu(self.master, tearoff = 0)
+        self.menubar.add_cascade(menu = self.menuedit,
+                label="Edit")
+        self.menuedit.add_command(
+                command = self.on_select_chapter,
+                label="Select chapter")
+
+    def on_exit(self):
+        self.master.destroy()
+
+    def on_open_data(self):
+        # open data - select TODO
+        pass
+        
+    def on_select_chapter(self):
+        # TODO
+        pass        
+        
+    def open_data_from(self, folder):
+        self.sim = petka.Engine()
+        self.sim.load_data(folder, "cp1251")
+        self.sim.open_part(0, 0)
+
+def main():
+    root = tkinter.Tk()
+    app = App(master = root)
+    if len(sys.argv) > 1:
+        fn = sys.argv[1]
+    else:
+        fn = "."
+    app.open_data_from(fn)
+    
+    app.mainloop()
+    #fman = petka.FileManager(".")
+    #fman.load_store("patch.str")
+    #fman.load_store("main.str")
+    #for k, v in fman.strtable.items():
+    #    print(k, "=", v)
+    # cleanup
+    #fman.unload_stores()
+    
+if __name__ == "__main__":
+    main()
diff --git a/engines/petka/petka/__init__.py b/engines/petka/petka/__init__.py
new file mode 100644
index 000000000..5bcfed3b5
--- /dev/null
+++ b/engines/petka/petka/__init__.py
@@ -0,0 +1,4 @@
+# pass
+
+from .fman import FileManager
+from .engine import Engine
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
new file mode 100644
index 000000000..69bf403b3
--- /dev/null
+++ b/engines/petka/petka/engine.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+
+# romiq.kh at gmail.com, 2014
+
+import os
+import struct
+
+from . import FileManager
+
+class Engine:
+    def __init__(self):
+        self.fman = None
+        self.parts = []
+        self.start_part = None
+        self.start_chap = None
+        self.start_scene = None
+
+        self.curr_part = None
+        self.curr_chap = None
+        
+        self.curr_path = None
+        self.curr_speech = None
+        self.curr_diskid = None
+        
+        
+    def parse_ini(self, f):
+        # parse ini settings
+        curr_sect = None
+        ini = {}
+        for line in f.readlines():
+            line = line.decode(self.enc).strip()
+            if len(line) == 0: continue
+            if line[:1] == ";": continue
+            if line[:1] == "[" and line[-1:] == "]":
+                curr_sect = line[1:-1].strip()
+                ini[curr_sect] = {}
+                continue
+            kv = line.split("=", 1)
+            if len(kv) != 2: continue
+            ini[curr_sect][kv[0].strip()] = kv[1].strip()
+        return ini
+        
+    def load_data(self, folder, enc):
+        self.fman = FileManager(folder)
+        self.enc = enc
+        # load PARTS.INI
+        pf = self.fman.find_path("parts.ini")
+        f = open(pf, "rb")
+        try:
+            self.parts_ini = self.parse_ini(f)
+        finally:
+            f.close()
+        if "All" in self.parts_ini:
+            if "Part" in self.parts_ini["All"]:
+                self.start_part = int(self.parts_ini["All"]["Part"])
+            if "Chapter" in self.parts_ini["All"]:
+                self.start_chap = int(self.parts_ini["All"]["Chapter"])
+        for sect, data in self.parts_ini.items():
+            if sect == "All":
+                if "Part" in data:
+                    self.start_part = int(data["Part"]) - 1
+                if "Chapter" in data:
+                    self.start_chap = int(data["Chapter"]) - 1
+            elif sect[:5] == "Part ":
+                self.parts.append(data)
+        # load BGS.INI
+        pf = self.fman.find_path("bgs.ini")
+        f = open(pf, "rb")
+        try:
+            self.bgs_ini = self.parse_ini(f)
+        finally:
+            f.close()
+        if "Settings" in self.bgs_ini:
+            if "StartRoom" in self.bgs_ini["Settings"]:
+                self.start_scene = self.bgs_ini["Settings"]["StartRoom"]
+
+    def open_part(self, part, chap):
+        pname = "Part {}".format(part)
+        pcname = pname
+        if chap:
+            pcname += " Chapter {}".format(chap)
+        self.curr_path = self.parts_ini[pname]["CurrentPath"]
+        self.curr_speech = self.parts_ini[pname]["PathSpeech"]
+        self.curr_diskid = self.parts_ini[pname]["DiskID"]
+        
+        
diff --git a/engines/petka/petka/fman.py b/engines/petka/petka/fman.py
new file mode 100644
index 000000000..ade282255
--- /dev/null
+++ b/engines/petka/petka/fman.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+# romiq.kh at gmail.com, 2014
+
+import os
+import struct
+
+# manage files data
+class FileManager:
+    def __init__(self, root):
+        self.root = root
+    
+        self.strfd = []
+        self.strtable = {}
+    
+    def find_path(self, path):
+        # search case insensive from root
+        dpath = []
+        npath = self.root
+        for item in path.split("/"):
+            if not item: continue
+            ok = False
+            for ritem in os.listdir(npath):
+                if item.lower() != ritem.lower(): continue
+                npath = os.path.join(npath, ritem)
+                ok = True
+                break
+            if not ok: return None
+        return npath
+        
+        
+    
+    def load_store(self, name):
+        path = self.find_path(name)
+        if path is None:
+            print("DEBUG: Store \"{}\" not found".format(name))
+            return
+        # scan table
+        f = open(path, "rb")
+        # check magic string "StOR"
+        magic = f.read(4)
+        if magic != b"StOR":
+            print("DEBUG: Bad magic in \"{}\"".format(name))
+            return
+        # read index table ref
+        temp = f.read(4)
+        index_ref = struct.unpack_from("<I", temp)[0]
+        f.seek(index_ref)
+        # index table length
+        temp = f.read(4)
+        index_len = struct.unpack_from("<I", temp)[0]
+        index_table = []
+        for iref in range(index_len):
+            temp = f.read(12) 
+            data = struct.unpack_from("<III", temp)
+            index_table.append((data[1], data[2]))
+        data = f.read().decode("ascii")
+        for idx, fname in enumerate(data.split("\x00")):
+            if idx < index_len and fname not in self.strtable:
+                self.strtable[fname] = (len(self.strfd), ) + \
+                    tuple(index_table[idx])
+        # add file descriptor
+        self.strfd.append((f, name))
+        
+        
+    def unload_stores(self):
+        for fd, name in self.strfd:
+            try:
+                if fd: fd.close()
+            except Exception as e:
+                print("DEBUG: Can't unload \"{}\":".format(name) + str(e))
+


Commit: f0008988a68e33186323a3ae80175ad8ead07268
    https://github.com/scummvm/scummvm-tools/commit/f0008988a68e33186323a3ae80175ad8ead07268
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Objects and scenes

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py
    engines/petka/petka/fman.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 9d7e1969e..720fe9876 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -64,6 +64,9 @@ class App(tkinter.Frame):
         self.sim = petka.Engine()
         self.sim.load_data(folder, "cp1251")
         self.sim.open_part(0, 0)
+        #self.sim.open_part(1, 0)
+        #self.sim.open_part(2, 0)
+        #self.sim.open_part(3, 0)
 
 def main():
     root = tkinter.Tk()
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 69bf403b3..ac7196309 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -7,6 +7,11 @@ import struct
 
 from . import FileManager
 
+class ScrObject:
+    def __init__(self, idx, name):
+        self.idx = idx
+        self.name = name
+
 class Engine:
     def __init__(self):
         self.fman = None
@@ -63,24 +68,107 @@ class Engine:
                     self.start_chap = int(data["Chapter"]) - 1
             elif sect[:5] == "Part ":
                 self.parts.append(data)
-        # load BGS.INI
-        pf = self.fman.find_path("bgs.ini")
-        f = open(pf, "rb")
-        try:
-            self.bgs_ini = self.parse_ini(f)
-        finally:
-            f.close()
-        if "Settings" in self.bgs_ini:
-            if "StartRoom" in self.bgs_ini["Settings"]:
-                self.start_scene = self.bgs_ini["Settings"]["StartRoom"]
+        # std stores
+        self.fman.load_store("patch.str")
+        self.fman.load_store("main.str")
 
     def open_part(self, part, chap):
+        self.fman.unload_stores(1)
         pname = "Part {}".format(part)
         pcname = pname
         if chap:
             pcname += " Chapter {}".format(chap)
-        self.curr_path = self.parts_ini[pname]["CurrentPath"]
-        self.curr_speech = self.parts_ini[pname]["PathSpeech"]
-        self.curr_diskid = self.parts_ini[pname]["DiskID"]
+        ini = self.parts_ini[pname]
+        self.curr_path = ini["CurrentPath"]
+        self.curr_speech = ini["PathSpeech"]
+        self.curr_diskid = ini["DiskID"]
         
+        # load BGS.INI
+        self.bgs_ini = {}
+        self.start_scene = None
+        pf = self.fman.find_path(self.curr_path + "bgs.ini")
+        print(self.curr_path)
+        if pf:
+            f = open(pf, "rb")
+            try:
+                self.bgs_ini = self.parse_ini(f)
+            finally:
+                f.close()
+            if "Settings" in self.bgs_ini:
+                if "StartRoom" in self.bgs_ini["Settings"]:
+                    self.start_scene = self.bgs_ini["Settings"]["StartRoom"]
+        # load .STR
+        strs = ["Flics", "Background", "Wav", "Music", "SFX"]
+        for strf in strs:
+            pf = self.fman.find_path(self.curr_path + "bgs.ini")
+            if not pf: continue
+            if strf in ini:
+                self.fman.load_store(ini[strf], 1)
+        # load script
+        self.load_script()
+        
+    def load_script(self):
+        self.objects = []
+        self.scenes = []
+        self.obj_idx = {}
+        self.scn_idx = {}
+
+        data = self.fman.read_data(self.curr_path + "script.dat")
+        num_obj, num_scn = struct.unpack_from("<II", data[:8])
+        off = 8
+        def read_rec(off):
+            obj_id, name_len = struct.unpack_from("<HI", data[off:off + 6])
+            off += 6
+            name = data[off:off + name_len].decode(self.enc)
+            off += name_len
+            num_act = struct.unpack_from("<I", data[off:off + 4])[0]
+            off += 4
+            acts = []
+            for i in range(num_act):
+                act_id, act_cond, act_arg, num_op = struct.unpack_from(\
+                    "<HBHI", data[off:off + 9])
+                off += 9
+                ops = []
+                for j in range(num_op):
+                    op = struct.unpack_from("<5H", data[off:off + 10])
+                    off += 10
+                    ops.append(op)
+                acts.append([act_id, act_cond, act_arg, ops])
+            rec = ScrObject(obj_id, name)
+            rec.acts = acts
+            return off, rec
         
+        for i in range(num_obj):
+            off, obj = read_rec(off)
+            self.objects.append(obj)
+            self.obj_idx[obj.idx] = obj
+
+        for i in range(num_scn):
+            off, scn = read_rec(off)
+            self.scenes.append(scn)
+            self.scn_idx[scn.idx] = scn
+            
+        data = self.fman.read_data(self.curr_path + "backgrnd.bg")
+        num_rec = struct.unpack_from("<I", data[:4])[0]
+        off = 4
+        for i in range(num_rec):
+            scn_ref, num_ref = struct.unpack_from("<HI", data[off:off + 6])
+            off += 6
+            if scn_ref in self.scn_idx:
+                scn = self.scn_idx[scn_ref]    
+                scn.refs = []
+            else:
+                print("DEBUG: Scene ID = 0x{:x} not found".format(scn_ref))
+                scn = None
+            for j in range(num_ref):
+                ref = struct.unpack_from("<H5I", data[off:off + 22])
+                off += 22
+                if scn:
+                    if ref[0] in self.obj_idx:
+                        obj = self.obj_idx[ref[0]]
+                        scn.refs.append([obj] + list(ref[1:]))
+                    else:
+                        print("DEBUG: Object ID = 0x{:x} not found".\
+                            format(obj[0]))
+
+
diff --git a/engines/petka/petka/fman.py b/engines/petka/petka/fman.py
index ade282255..0a59ca398 100644
--- a/engines/petka/petka/fman.py
+++ b/engines/petka/petka/fman.py
@@ -17,6 +17,7 @@ class FileManager:
         # search case insensive from root
         dpath = []
         npath = self.root
+        path = path.replace("\\", "/")
         for item in path.split("/"):
             if not item: continue
             ok = False
@@ -30,7 +31,7 @@ class FileManager:
         
         
     
-    def load_store(self, name):
+    def load_store(self, name, tag = 0):
         path = self.find_path(name)
         if path is None:
             print("DEBUG: Store \"{}\" not found".format(name))
@@ -56,15 +57,34 @@ class FileManager:
             index_table.append((data[1], data[2]))
         data = f.read().decode("ascii")
         for idx, fname in enumerate(data.split("\x00")):
-            if idx < index_len and fname not in self.strtable:
-                self.strtable[fname] = (len(self.strfd), ) + \
-                    tuple(index_table[idx])
+            if idx < index_len and fname.lower() not in self.strtable:
+                self.strtable[fname.lower()] = (len(self.strfd),) + index_table[idx]
         # add file descriptor
-        self.strfd.append((f, name))
+        self.strfd.append((f, name, tag))
         
+    def read_data(self, fname):
+        sf = fname.lower()
+        if sf in self.strtable:
+            fnum, st, ln = self.strtable[sf]
+            self.strfd[fnum][0].seek(st)
+            return self.strfd[fnum][0].read(ln)
+        else:
+            pf = self.find_path(fname)
+            if not pf:
+                print("DEBUG: Can't open file \"{}\"".format(fname))
+            # file in filesystem
+            f = open(pf, "rb")
+            try:
+                data = f.read()
+            finally:
+                f.close()
+            return data
+            
         
-    def unload_stores(self):
-        for fd, name in self.strfd:
+    def unload_stores(self, flt = None):
+        for fd, name, tag in self.strfd:
+            if flt is not None:
+                if tag != flt: continue
             try:
                 if fd: fd.close()
             except Exception as e:


Commit: 9c043f222a4067d1a299b4534848a8b6e5bbfdae
    https://github.com/scummvm/scummvm-tools/commit/9c043f222a4067d1a299b4534848a8b6e5bbfdae
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Fix from loading DEMO

Changed paths:
    engines/petka/petka/engine.py


diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index ac7196309..c7930e35d 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -50,38 +50,45 @@ class Engine:
         self.enc = enc
         # load PARTS.INI
         pf = self.fman.find_path("parts.ini")
-        f = open(pf, "rb")
-        try:
-            self.parts_ini = self.parse_ini(f)
-        finally:
-            f.close()
-        if "All" in self.parts_ini:
-            if "Part" in self.parts_ini["All"]:
-                self.start_part = int(self.parts_ini["All"]["Part"])
-            if "Chapter" in self.parts_ini["All"]:
-                self.start_chap = int(self.parts_ini["All"]["Chapter"])
-        for sect, data in self.parts_ini.items():
-            if sect == "All":
-                if "Part" in data:
-                    self.start_part = int(data["Part"]) - 1
-                if "Chapter" in data:
-                    self.start_chap = int(data["Chapter"]) - 1
-            elif sect[:5] == "Part ":
-                self.parts.append(data)
+        if pf:
+            f = open(pf, "rb")
+            try:
+                self.parts_ini = self.parse_ini(f)
+            finally:
+                f.close()
+            for sect, data in self.parts_ini.items():
+                if sect == "All":
+                    if "Part" in data:
+                        self.start_part = int(data["Part"]) - 1
+                    if "Chapter" in data:
+                        self.start_chap = int(data["Chapter"]) - 1
+                elif sect[:5] == "Part ":
+                    self.parts.append(data)
+        else:
+            # load BGS.INI only (e.g. DEMO)
+            self.parts_ini = None
+            
         # std stores
         self.fman.load_store("patch.str")
         self.fman.load_store("main.str")
 
     def open_part(self, part, chap):
         self.fman.unload_stores(1)
-        pname = "Part {}".format(part)
-        pcname = pname
-        if chap:
-            pcname += " Chapter {}".format(chap)
-        ini = self.parts_ini[pname]
-        self.curr_path = ini["CurrentPath"]
-        self.curr_speech = ini["PathSpeech"]
-        self.curr_diskid = ini["DiskID"]
+        if self.parts_ini:
+            pname = "Part {}".format(part)
+            pcname = pname
+            if chap:
+                pcname += " Chapter {}".format(chap)
+            ini = self.parts_ini[pname]
+            self.curr_path = ini["CurrentPath"]
+            self.curr_speech = ini["PathSpeech"]
+            self.curr_diskid = ini["DiskID"]
+        else:
+            ini = {}
+            self.curr_path = ""
+            self.curr_speech = ""
+            self.curr_diskid = None
+
         
         # load BGS.INI
         self.bgs_ini = {}
@@ -171,4 +178,3 @@ class Engine:
                         print("DEBUG: Object ID = 0x{:x} not found".\
                             format(obj[0]))
 
-


Commit: 8f3e8e5d64de2e8c7d6a71147be534b841d1d491
    https://github.com/scummvm/scummvm-tools/commit/8f3e8e5d64de2e8c7d6a71147be534b841d1d491
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Canvas rendering, BMP loading

Changed paths:
  A engines/petka/petka/imgbmp.py
    engines/petka/p12explore.py
    engines/petka/petka/__init__.py
    engines/petka/petka/engine.py
    engines/petka/petka/fman.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 720fe9876..4fa371dd8 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -5,7 +5,7 @@
 
 import sys, os
 import tkinter
-from tkinter import ttk
+from tkinter import ttk, font
 
 import petka
 
@@ -16,42 +16,211 @@ class App(tkinter.Frame):
         tkinter.Frame.__init__(self, master)
         master.title(APPNAME)
         self.pack(fill = tkinter.BOTH, expand = 1)
-        self.createWidgets()
-        self.createMenu()
-        
+        #self.createWidgets()
+        #self.createMenu()
+        self.pad = None
         self.sim = None
+        self.curr_mode = 0 
+        self.curr_width = 0
+        self.curr_height = 0
+        self.last_width = 1
+        self.last_height = 1
+        self.need_update = False
+        self.main_image = tkinter.PhotoImage(width = 1, height = 1)
+        self.after_idle(self.on_first_display)
         
-    def createWidgets(self):
+    def create_widgets(self):
         
         ttk.Style().configure("Tool.TButton", width = -1) # minimal width
-        #ttk.Style().configure("TLabel", padding = PADDING)
+        ttk.Style().configure("TLabel", padding = self.pad)
+
+        # canvas
+        self.frm_view = ttk.Frame(self)
+        self.frm_view.pack(side = tkinter.TOP, expand = 1, fill = tkinter.BOTH)
+        self.frm_view.grid_rowconfigure(0, weight = 1)
+        self.frm_view.grid_columnconfigure(0, weight = 1)
+        self.scr_view_x = ttk.Scrollbar(self.frm_view, 
+            orient = tkinter.HORIZONTAL)
+        self.scr_view_x.grid(row = 1, column = 0, \
+            sticky = tkinter.E + tkinter.W)
+        self.scr_view_y = ttk.Scrollbar(self.frm_view)
+        self.scr_view_y.grid(row = 0, column = 1, sticky = \
+            tkinter.N + tkinter.S)
+        self.canv_view = tkinter.Canvas(self.frm_view, height = 150, 
+            scrollregion = (0, 0, 50, 50),
+            xscrollcommand = self.scr_view_x.set,
+            yscrollcommand = self.scr_view_y.set)
+        self.canv_view_w = 0
+        self.canv_view_h = 0
+        self.canv_view_fact = 1
+        self.canv_view.grid(row = 0, column = 0, \
+            sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
+        self.scr_view_x.config(command = self.canv_view.xview)
+        self.scr_view_y.config(command = self.canv_view.yview)
+        # don't forget
+        #   canvas.config(scrollregion=(left, top, right, bottom))
+        self.canv_view.bind('<Configure>', self.on_resize_view)
+        self.canv_view.bind('<ButtonPress-1>', self.on_mouse_view)
 
+        self.update_after()
+        self.update_gui(None)
 
-    def createMenu(self):
+    def create_menu(self):
         self.menubar = tkinter.Menu(self.master)
         self.master.configure(menu = self.menubar)
 
         self.menufile = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menufile,
-                label="File")
+                label = "File")
         self.menufile.add_command(
                 command = self.on_open_data,
-                label="Open data...")
+                label = "Open data...")
         self.menufile.add_separator()
         self.menufile.add_command(
                 command = self.on_exit,
-                label="Quit")    
+                label = "Quit")    
 
         self.menuedit = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menuedit,
-                label="Edit")
+                label = "Edit")
         self.menuedit.add_command(
                 command = self.on_select_chapter,
-                label="Select chapter")
+                label = "Select chapter")
+
+    def update_after(self):
+        if not self.need_update:
+            self.after_idle(self.on_idle)
+            self.need_update = True
+
+    def on_idle(self):
+        self.need_update = False
+        self.update_canvas()
+
+    def on_first_display(self):
+        fnt = font.Font()
+        try:
+            self.pad = fnt.measure(":")
+        except:
+            self.pad = 5
+        self.create_widgets()
+        self.create_menu()
 
     def on_exit(self):
         self.master.destroy()
 
+    def on_mouse_view(self, event):
+        #self.currMode += 1
+        #if self.currMode > 1:
+        #    self.currMode = 0
+        self.last_width = -1
+        self.last_height = -1
+        self.update_after()
+        
+    def on_resize_view(self, event):
+        self.canv_view_w = event.width
+        self.canv_view_h = event.height
+        self.update_after()
+
+    def update_canvas(self):
+        # rebuild image
+        c = self.canv_view
+        c.delete(tkinter.ALL)
+
+        if self.sim is None: return
+                    
+        #if (self.last_width != self.curr_width) or \
+        #   (self.last_height != self.curr_height):
+        #       self.build_image()
+        
+        # Preview image        
+        #print("Update %d x %d" % (self.currWidth, self.currHeight))
+        self.canv_image = self.main_image.copy()
+        w = self.canv_view_w
+        h = self.canv_view_h
+        if (w == 0) and (h == 0): 
+            return
+        
+        scale = 0 #self.RadioGroupScale.get()
+        if scale == 0: # Fit
+            try:
+                psc = w / h
+                isc = self.curr_width / self.curr_height
+                if psc < isc:
+                    if w > self.curr_width:
+                        fact = w // self.curr_width
+                    else:
+                        fact = -self.curr_width // w
+                else:
+                    if h > self.curr_height:
+                        fact = h // self.curr_height
+                    else:
+                        fact = -self.curr_height // h
+            except:
+                fact = 1
+        else:
+            fact = scale
+
+        # place on canvas
+        if fact > 0:
+            pw = self.curr_width * fact
+            ph = self.curr_height * fact
+        else:
+            pw = self.curr_width // -fact
+            ph = self.curr_height // -fact
+
+        cw = max(pw, w)
+        ch = max(ph, h)
+    
+        c.config(scrollregion = (0, 0, cw - 2, ch - 2))
+    
+        if fact > 0:
+            self.canv_image = self.canv_image.zoom(fact)
+        else:
+            self.canv_image = self.canv_image.subsample(-fact)
+        self.canv_image_fact = fact
+        #print("Place c %d %d, p %d %d" % (cw, ch, w, h))
+        c.create_image(cw // 2, ch // 2, image = self.canv_image)
+
+    def build_image(self):
+        # rebuild main_image
+        width = self.curr_width
+        height = self.curr_height
+        self.last_width = width
+        self.last_height = height
+
+        return
+        
+    def make_image(self, width, height, data):
+        # create P6
+        phdr = ("P6\n{} {}\n255\n".format(width, height))
+        rawlen = width * height * 3 # RGB
+        #phdr = ("P5\n{} {}\n255\n".format(width, height))
+        #rawlen = width * height
+        phdr = phdr.encode("UTF-8")
+
+        if len(data) > rawlen:
+            # truncate
+            pdata = data[:rawlen]
+        if len(data) < rawlen:
+            # fill gap
+            gap = bytearray()
+            data += b"\xff" * (rawlen - len(data))
+        p = bytearray(phdr)
+        # fix UTF-8 issue
+        for ch in data:
+            if ch > 0x7f:
+                p += bytes((0b11000000 |\
+                    ch >> 6, 0b10000000 |\
+                    (ch & 0b00111111)))               
+            else:
+                p += bytes((ch,))
+        image = tkinter.PhotoImage(width = width, height = height, \
+            data = bytes(p))
+        return image                
+
+    def update_gui(self, fn):
+        pass
+
     def on_open_data(self):
         # open data - select TODO
         pass
@@ -64,6 +233,15 @@ class App(tkinter.Frame):
         self.sim = petka.Engine()
         self.sim.load_data(folder, "cp1251")
         self.sim.open_part(0, 0)
+        # load static image
+        #for item in self.sim.fman.strtable:
+        #    print(item)
+        bmpdata = self.sim.fman.read_file("MAIN/INTRFACE.BG/INSTHERO.BMP")
+        bmp = petka.BMPLoader()
+        bmp.load_data(bmpdata)
+        self.main_image = self.make_image(640, 480, bmp.rgb)
+        self.curr_width = 640
+        self.curr_height = 480
         #self.sim.open_part(1, 0)
         #self.sim.open_part(2, 0)
         #self.sim.open_part(3, 0)
diff --git a/engines/petka/petka/__init__.py b/engines/petka/petka/__init__.py
index 5bcfed3b5..ecf231162 100644
--- a/engines/petka/petka/__init__.py
+++ b/engines/petka/petka/__init__.py
@@ -1,4 +1,6 @@
-# pass
 
-from .fman import FileManager
+class EngineError(Exception): pass
+
 from .engine import Engine
+from .fman import FileManager
+from .imgbmp import BMPLoader
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index c7930e35d..10a095408 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -5,7 +5,7 @@
 import os
 import struct
 
-from . import FileManager
+from .fman import FileManager
 
 class ScrObject:
     def __init__(self, idx, name):
@@ -120,7 +120,7 @@ class Engine:
         self.obj_idx = {}
         self.scn_idx = {}
 
-        data = self.fman.read_data(self.curr_path + "script.dat")
+        data = self.fman.read_file(self.curr_path + "script.dat")
         num_obj, num_scn = struct.unpack_from("<II", data[:8])
         off = 8
         def read_rec(off):
@@ -155,7 +155,7 @@ class Engine:
             self.scenes.append(scn)
             self.scn_idx[scn.idx] = scn
             
-        data = self.fman.read_data(self.curr_path + "backgrnd.bg")
+        data = self.fman.read_file(self.curr_path + "backgrnd.bg")
         num_rec = struct.unpack_from("<I", data[:4])[0]
         off = 4
         for i in range(num_rec):
@@ -165,16 +165,16 @@ class Engine:
                 scn = self.scn_idx[scn_ref]    
                 scn.refs = []
             else:
-                print("DEBUG: Scene ID = 0x{:x} not found".format(scn_ref))
-                scn = None
+                raise EngineError("DEBUG: Scene ID = 0x{:x} not found".\
+                    format(scn_ref))
+
             for j in range(num_ref):
                 ref = struct.unpack_from("<H5I", data[off:off + 22])
                 off += 22
-                if scn:
-                    if ref[0] in self.obj_idx:
-                        obj = self.obj_idx[ref[0]]
-                        scn.refs.append([obj] + list(ref[1:]))
-                    else:
-                        print("DEBUG: Object ID = 0x{:x} not found".\
-                            format(obj[0]))
+                if ref[0] in self.obj_idx:
+                    obj = self.obj_idx[ref[0]]
+                    scn.refs.append([obj] + list(ref[1:]))
+                else:
+                    raise EngineError("DEBUG: Object ID = 0x{:x} not found".\
+                        format(obj[0]))
 
diff --git a/engines/petka/petka/fman.py b/engines/petka/petka/fman.py
index 0a59ca398..400cf1a78 100644
--- a/engines/petka/petka/fman.py
+++ b/engines/petka/petka/fman.py
@@ -5,6 +5,8 @@
 import os
 import struct
 
+from . import EngineError
+
 # manage files data
 class FileManager:
     def __init__(self, root):
@@ -41,7 +43,7 @@ class FileManager:
         # check magic string "StOR"
         magic = f.read(4)
         if magic != b"StOR":
-            print("DEBUG: Bad magic in \"{}\"".format(name))
+            raise EngineError("Bad magic in store \"{}\"".format(name))
             return
         # read index table ref
         temp = f.read(4)
@@ -55,20 +57,29 @@ class FileManager:
             temp = f.read(12) 
             data = struct.unpack_from("<III", temp)
             index_table.append((data[1], data[2]))
-        data = f.read().decode("ascii")
+        data = f.read().decode("latin-1")
         for idx, fname in enumerate(data.split("\x00")):
-            if idx < index_len and fname.lower() not in self.strtable:
-                self.strtable[fname.lower()] = (len(self.strfd),) + index_table[idx]
+            fname = fname.lower().replace("\\", "/")
+            if idx < index_len and fname not in self.strtable:
+                self.strtable[fname] = (len(self.strfd),) + \
+                    index_table[idx]
+            else:
+                if len(fname) > 0:
+                    raise EngineError("Extra file record \"{}\" in \"{}\"".\
+                        format(fname, name))
         # add file descriptor
         self.strfd.append((f, name, tag))
         
-    def read_data(self, fname):
-        sf = fname.lower()
+    def read_file(self, fname):
+        sf = fname.lower().replace("\\", "/")
         if sf in self.strtable:
             fnum, st, ln = self.strtable[sf]
+            print("Load file \"{}\" from store \"{}\"".\
+                format(fname, self.strfd[fnum][1]))
             self.strfd[fnum][0].seek(st)
             return self.strfd[fnum][0].read(ln)
         else:
+            print("Load file \"{}\" from filesystem".format(fname))
             pf = self.find_path(fname)
             if not pf:
                 print("DEBUG: Can't open file \"{}\"".format(fname))
@@ -82,9 +93,10 @@ class FileManager:
             
         
     def unload_stores(self, flt = None):
-        for fd, name, tag in self.strfd:
+        for idx, (fd, name, tag) in enumerate(self.strfd):
             if flt is not None:
                 if tag != flt: continue
+            print("DEBIG: Unload store \"{}\"".format(name))
             try:
                 if fd: fd.close()
             except Exception as e:
diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
new file mode 100644
index 000000000..4c5b36296
--- /dev/null
+++ b/engines/petka/petka/imgbmp.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+
+# romiq.kh at gmail.com, 2014
+
+import array, struct
+
+from . import EngineError
+
+class BMPLoader:
+    def __init__(self):
+        self.raw = None
+        
+    def load_data(self, data):
+        # TODO: normal BMP, rle BMP
+        # check magic string "BM"
+        if data[:2] != b"BM":
+            raise EngineError("Bad magic string")
+        off = 2
+        
+        f_sz, res1, res2, data_offset = struct.unpack_from("<IHHI", \
+            data[off:off + 12])
+        off += 12
+        
+        # read next 40 bytes, BITMAPINFOHEADER
+        pict = struct.unpack_from("<IiiHHIIiiII", data[off:off + 40])
+        off += 40
+        if pict[0] != 40:
+            raise EngineError("Unsupported InfoHeader")
+        pictw = pict[1]
+        picth = pict[2]
+        
+        # read data_offset - 40 - 6 bytes
+        delta = data_offset - 40 - 6
+        if delta < 0:
+            raise EngineError("To small bitmap data offset")
+        if delta != 8:
+            raise EngineError("Unsupported Header at 0x36")
+        hdr36 = struct.unpack_from("<II", data[off:off + delta])
+        off += delta
+
+        bsz = pictw * picth * 2
+        picture_data = data[off:off + bsz]
+        off += bsz
+        if len(picture_data) != bsz:
+            raise EngineError("Bitmap truncated, need {}, got {}".format(bsz, \
+                len(picture_data)))
+
+        # read 2 zero bytes
+        if data[off:off + 2] != b"\x00\x00":
+            raise EngineError("Magic zero bytes absent [{:02x},{:02x}]".\
+                format(data[off], data[off + 1]))
+        off += 2
+
+        if len(data) - off > 0:
+            raise EngineError("BMP read error, some data unparsed")
+                
+        # convert 16 bit to 24
+        b16arr = array.array("H") # unsigned short
+        b16arr.frombytes(picture_data)
+        rgb = array.array("B")
+        for b16 in b16arr:
+            rgb.append((b16 >> 5) & 0b11111000)
+            rgb.append((b16 << 5) & 0b11100000 | (b16 >> 11) & 0b00011100)
+            rgb.append((b16 << 0) &0b11111000) 
+        # Y-mirror
+        self.rgb = array.array("B")
+        for i in range(picth):
+            off = (picth - i - 1) * pictw * 3
+            self.rgb += rgb[off:off + pictw * 3]
+        


Commit: 37c26c9520b773b0d8c882ee5760e5344d8baa74
    https://github.com/scummvm/scummvm-tools/commit/37c26c9520b773b0d8c882ee5760e5344d8baa74
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Resource list

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 4fa371dd8..f3aee0468 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -26,6 +26,7 @@ class App(tkinter.Frame):
         self.last_width = 1
         self.last_height = 1
         self.need_update = False
+        self.curr_gui = []
         self.main_image = tkinter.PhotoImage(width = 1, height = 1)
         self.after_idle(self.on_first_display)
         
@@ -33,6 +34,10 @@ class App(tkinter.Frame):
         
         ttk.Style().configure("Tool.TButton", width = -1) # minimal width
         ttk.Style().configure("TLabel", padding = self.pad)
+        
+        # leftpanel
+        self.frm_left = ttk.Frame(self)
+        self.frm_left.pack(side = tkinter.LEFT, expand = 0, fill = tkinter.BOTH)
 
         # canvas
         self.frm_view = ttk.Frame(self)
@@ -219,7 +224,17 @@ class App(tkinter.Frame):
         return image                
 
     def update_gui(self, fn):
-        pass
+        # TODO: remove unused gui items
+        
+        if self.curr_mode == 100:
+            # list resources
+            lst = tkinter.Listbox(self.frm_left)
+            lst.pack(expand = 1, fill = tkinter.BOTH)
+            self.curr_gui.append(lst)
+            # fill
+            for res_id in self.sim.resord:
+                lst.insert(tkinter.END, "{} [{}]".format(self.sim.res[res_id],
+                    res_id))
 
     def on_open_data(self):
         # open data - select TODO
@@ -236,12 +251,13 @@ class App(tkinter.Frame):
         # load static image
         #for item in self.sim.fman.strtable:
         #    print(item)
-        bmpdata = self.sim.fman.read_file("MAIN/INTRFACE.BG/INSTHERO.BMP")
-        bmp = petka.BMPLoader()
-        bmp.load_data(bmpdata)
-        self.main_image = self.make_image(640, 480, bmp.rgb)
-        self.curr_width = 640
-        self.curr_height = 480
+        #bmpdata = self.sim.fman.read_file("MAIN/INTRFACE.BG/INSTHERO.BMP")
+        #bmp = petka.BMPLoader()
+        #bmp.load_data(bmpdata)
+        #self.main_image = self.make_image(640, 480, bmp.rgb)
+        #self.curr_width = 640
+        #self.curr_height = 480
+        self.curr_mode = 100
         #self.sim.open_part(1, 0)
         #self.sim.open_part(2, 0)
         #self.sim.open_part(3, 0)
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 10a095408..5dc459d8a 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -4,6 +4,7 @@
 
 import os
 import struct
+import io
 
 from .fman import FileManager
 
@@ -45,6 +46,24 @@ class Engine:
             ini[curr_sect][kv[0].strip()] = kv[1].strip()
         return ini
         
+    def parse_res(self, f):
+        res  = {}
+        resord = []
+        for line in f.readlines():
+            line = line.decode(self.enc).strip()
+            if len(line) == 0:
+                continue
+            pair = line.split("=", 1)
+            if len(pair) < 2:
+                continue
+            value = pair[1].strip()
+            if value[:1] == "=":
+                value = value[1:].strip()
+            res_id = int(pair[0].strip(), 10)
+            res[res_id] = value
+            resord.append(res_id)
+        return res, resord
+        
     def load_data(self, folder, enc):
         self.fman = FileManager(folder)
         self.enc = enc
@@ -177,4 +196,12 @@ class Engine:
                 else:
                     raise EngineError("DEBUG: Object ID = 0x{:x} not found".\
                         format(obj[0]))
+                        
+        data = self.fman.read_file(self.curr_path + "resource.qrc")
+        mems = io.BytesIO()
+        mems.write(data)
+        mems.seek(0)
+        self.res, self.resord = self.parse_res(mems)
+        
+        
 


Commit: 7d64e0d2b5148b2dfd7c0b6869d8718d1b075a13
    https://github.com/scummvm/scummvm-tools/commit/7d64e0d2b5148b2dfd7c0b6869d8718d1b075a13
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Select part, select resource

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py
    engines/petka/petka/imgbmp.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index f3aee0468..f20876482 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -16,8 +16,6 @@ class App(tkinter.Frame):
         tkinter.Frame.__init__(self, master)
         master.title(APPNAME)
         self.pack(fill = tkinter.BOTH, expand = 1)
-        #self.createWidgets()
-        #self.createMenu()
         self.pad = None
         self.sim = None
         self.curr_mode = 0 
@@ -26,6 +24,9 @@ class App(tkinter.Frame):
         self.last_width = 1
         self.last_height = 1
         self.need_update = False
+        self.canv_view_w = 0
+        self.canv_view_h = 0
+        self.canv_view_fact = 1
         self.curr_gui = []
         self.main_image = tkinter.PhotoImage(width = 1, height = 1)
         self.after_idle(self.on_first_display)
@@ -35,13 +36,17 @@ class App(tkinter.Frame):
         ttk.Style().configure("Tool.TButton", width = -1) # minimal width
         ttk.Style().configure("TLabel", padding = self.pad)
         
+        # main paned
+        self.pan_main = ttk.PanedWindow(self, orient = tkinter.HORIZONTAL)
+        self.pan_main.pack(fill = tkinter.BOTH, expand = 1)
+        
         # leftpanel
-        self.frm_left = ttk.Frame(self)
-        self.frm_left.pack(side = tkinter.LEFT, expand = 0, fill = tkinter.BOTH)
+        self.frm_left = ttk.Frame(self.pan_main)
+        self.pan_main.add(self.frm_left)
 
         # canvas
-        self.frm_view = ttk.Frame(self)
-        self.frm_view.pack(side = tkinter.TOP, expand = 1, fill = tkinter.BOTH)
+        self.frm_view = ttk.Frame(self.pan_main)
+        self.pan_main.add(self.frm_view)
         self.frm_view.grid_rowconfigure(0, weight = 1)
         self.frm_view.grid_columnconfigure(0, weight = 1)
         self.scr_view_x = ttk.Scrollbar(self.frm_view, 
@@ -55,9 +60,6 @@ class App(tkinter.Frame):
             scrollregion = (0, 0, 50, 50),
             xscrollcommand = self.scr_view_x.set,
             yscrollcommand = self.scr_view_y.set)
-        self.canv_view_w = 0
-        self.canv_view_h = 0
-        self.canv_view_fact = 1
         self.canv_view.grid(row = 0, column = 0, \
             sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
         self.scr_view_x.config(command = self.canv_view.xview)
@@ -68,7 +70,7 @@ class App(tkinter.Frame):
         self.canv_view.bind('<ButtonPress-1>', self.on_mouse_view)
 
         self.update_after()
-        self.update_gui(None)
+        self.update_gui()
 
     def create_menu(self):
         self.menubar = tkinter.Menu(self.master)
@@ -89,8 +91,12 @@ class App(tkinter.Frame):
         self.menubar.add_cascade(menu = self.menuedit,
                 label = "Edit")
         self.menuedit.add_command(
-                command = self.on_select_chapter,
-                label = "Select chapter")
+                command = self.on_list_parts,
+                label = "Select part")
+        self.menuedit.add_separator()
+        self.menuedit.add_command(
+                command = self.on_list_res,
+                label = "Resources")
 
     def update_after(self):
         if not self.need_update:
@@ -223,41 +229,106 @@ class App(tkinter.Frame):
             data = bytes(p))
         return image                
 
-    def update_gui(self, fn):
+    def update_gui_add_left_listbox(self, text):
+        lab = tkinter.Label(self.frm_left, text = text)
+        lab.pack()
+        
+        frm_lb = ttk.Frame(self.frm_left)
+        frm_lb.pack(fill = tkinter.BOTH, expand = 1)
+        frm_lb.grid_rowconfigure(0, weight = 1)
+        frm_lb.grid_columnconfigure(0, weight = 1)
+        scr_lb_x = ttk.Scrollbar(frm_lb, orient = tkinter.HORIZONTAL)
+        scr_lb_x.grid(row = 1, column = 0, sticky = tkinter.E + tkinter.W)
+        scr_lb_y = ttk.Scrollbar(frm_lb)
+        scr_lb_y.grid(row = 0, column = 1, sticky = tkinter.N + tkinter.S)
+        lb = tkinter.Listbox(frm_lb,
+            xscrollcommand = scr_lb_x.set,
+            yscrollcommand = scr_lb_y.set)
+        lb.grid(row = 0, column = 0, \
+            sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
+        scr_lb_x.config(command = lb.xview)
+        scr_lb_y.config(command = lb.yview)
+        self.curr_gui.append(lambda:lb.grid_remove())
+        self.curr_gui.append(lambda:lab.pack_forget())
+        self.curr_gui.append(lambda:frm_lb.pack_forget())
+        lb.bind("<Double-Button-1>", self.on_left_listbox)
+        lb.bind("<Return>", self.on_left_listbox)
+        return lb
+
+    def update_gui(self):
         # TODO: remove unused gui items
+        for item in self.curr_gui:
+            item()
         
-        if self.curr_mode == 100:
+        if self.curr_mode == 90:
+            # list parts
+            lb = self.update_gui_add_left_listbox("Parts")   
+            for part in self.sim.parts:
+                lb.insert(tkinter.END, part)
+            self.curr_lb = lb
+        elif self.curr_mode == 100:
             # list resources
-            lst = tkinter.Listbox(self.frm_left)
-            lst.pack(expand = 1, fill = tkinter.BOTH)
-            self.curr_gui.append(lst)
-            # fill
+            lb = self.update_gui_add_left_listbox("Resources")   
             for res_id in self.sim.resord:
-                lst.insert(tkinter.END, "{} [{}]".format(self.sim.res[res_id],
-                    res_id))
+                lb.insert(tkinter.END, "{} - {}".format(res_id, \
+                    self.sim.res[res_id]))
+            self.curr_lb = lb
+
+    def on_left_listbox(self, event):
+        if self.curr_mode == 90:
+            # parts
+            try:
+                part_id = self.curr_lb.curselection()[0]
+                part_id = int(part_id)
+            except:
+                pass
+            part_id = self.sim.parts[part_id]
+            # parse
+            pnum = part_id[5:]
+            cnum = pnum.split("Chapter", 1)
+            if len(cnum) > 1:
+                pnum = int(cnum[0].strip(), 10)
+                cnum = int(cnum[0].strip(), 10)
+            else:
+                cnum = 0
+            self.sim.open_part(pnum, cnum)
+            self.update_after()
+        elif self.curr_mode == 100:
+            # resources
+            try:
+                res_id = self.curr_lb.curselection()[0]
+                res_id = int(res_id)
+            except:
+                pass
+            res_id = self.sim.resord[res_id]
+            fn = self.sim.res[res_id]
+            if fn[-4:].lower() == ".bmp":
+                bmpdata = self.sim.fman.read_file(fn)
+                bmp = petka.BMPLoader()
+                bmp.load_data(bmpdata)
+                self.main_image = self.make_image(bmp.width, bmp.height, bmp.rgb)
+                self.curr_width = bmp.width
+                self.curr_height = bmp.height
+                self.update_after()
+            print(fn)
 
     def on_open_data(self):
         # open data - select TODO
         pass
         
-    def on_select_chapter(self):
-        # TODO
-        pass        
+    def on_list_parts(self):
+        self.curr_mode = 90
+        self.update_gui()
+
+    def on_list_res(self):
+        self.curr_mode = 100
+        self.update_gui()
         
     def open_data_from(self, folder):
         self.sim = petka.Engine()
         self.sim.load_data(folder, "cp1251")
         self.sim.open_part(0, 0)
-        # load static image
-        #for item in self.sim.fman.strtable:
-        #    print(item)
-        #bmpdata = self.sim.fman.read_file("MAIN/INTRFACE.BG/INSTHERO.BMP")
-        #bmp = petka.BMPLoader()
-        #bmp.load_data(bmpdata)
-        #self.main_image = self.make_image(640, 480, bmp.rgb)
-        #self.curr_width = 640
-        #self.curr_height = 480
-        self.curr_mode = 100
+        self.curr_mode = 90
         #self.sim.open_part(1, 0)
         #self.sim.open_part(2, 0)
         #self.sim.open_part(3, 0)
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 5dc459d8a..48955e95b 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -33,6 +33,7 @@ class Engine:
         # parse ini settings
         curr_sect = None
         ini = {}
+        ordersect = []
         for line in f.readlines():
             line = line.decode(self.enc).strip()
             if len(line) == 0: continue
@@ -40,10 +41,12 @@ class Engine:
             if line[:1] == "[" and line[-1:] == "]":
                 curr_sect = line[1:-1].strip()
                 ini[curr_sect] = {}
+                ordersect.append(curr_sect)
                 continue
             kv = line.split("=", 1)
             if len(kv) != 2: continue
             ini[curr_sect][kv[0].strip()] = kv[1].strip()
+        ini["__order__"] = ordersect
         return ini
         
     def parse_res(self, f):
@@ -75,14 +78,15 @@ class Engine:
                 self.parts_ini = self.parse_ini(f)
             finally:
                 f.close()
-            for sect, data in self.parts_ini.items():
+            for sect in self.parts_ini["__order__"]:
+                data = self.parts_ini[sect]
                 if sect == "All":
                     if "Part" in data:
                         self.start_part = int(data["Part"]) - 1
                     if "Chapter" in data:
                         self.start_chap = int(data["Chapter"]) - 1
                 elif sect[:5] == "Part ":
-                    self.parts.append(data)
+                    self.parts.append(sect)
         else:
             # load BGS.INI only (e.g. DEMO)
             self.parts_ini = None
diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
index 4c5b36296..8cfb95aca 100644
--- a/engines/petka/petka/imgbmp.py
+++ b/engines/petka/petka/imgbmp.py
@@ -9,6 +9,8 @@ from . import EngineError
 class BMPLoader:
     def __init__(self):
         self.raw = None
+        self.width = 0
+        self.height = 0
         
     def load_data(self, data):
         # TODO: normal BMP, rle BMP
@@ -27,7 +29,9 @@ class BMPLoader:
         if pict[0] != 40:
             raise EngineError("Unsupported InfoHeader")
         pictw = pict[1]
+        self.width = pictw
         picth = pict[2]
+        self.height = picth
         
         # read data_offset - 40 - 6 bytes
         delta = data_offset - 40 - 6


Commit: 9405a96b3a4a7fd21ae2550663a34ed58913b1ca
    https://github.com/scummvm/scummvm-tools/commit/9405a96b3a4a7fd21ae2550663a34ed58913b1ca
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Fix unloading stores

Changed paths:
    engines/petka/petka/fman.py
    engines/petka/petka/imgbmp.py


diff --git a/engines/petka/petka/fman.py b/engines/petka/petka/fman.py
index 400cf1a78..f61d5ed7c 100644
--- a/engines/petka/petka/fman.py
+++ b/engines/petka/petka/fman.py
@@ -93,12 +93,21 @@ class FileManager:
             
         
     def unload_stores(self, flt = None):
+        strfd = []
+        strtable = {}
         for idx, (fd, name, tag) in enumerate(self.strfd):
             if flt is not None:
-                if tag != flt: continue
-            print("DEBIG: Unload store \"{}\"".format(name))
+                if tag != flt: 
+                    for k, v in self.strtable:
+                        if v == idx:
+                            strtable[k] = len(strfd)
+                    strfd.append((fd, name, tag))
+                    continue
+            print("DEBUG: Unload store \"{}\"".format(name))
             try:
                 if fd: fd.close()
             except Exception as e:
                 print("DEBUG: Can't unload \"{}\":".format(name) + str(e))
-
+        self.strff = strfd
+        self.strtable = strtable
+        
diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
index 8cfb95aca..eaf849fc3 100644
--- a/engines/petka/petka/imgbmp.py
+++ b/engines/petka/petka/imgbmp.py
@@ -51,8 +51,7 @@ class BMPLoader:
 
         # read 2 zero bytes
         if data[off:off + 2] != b"\x00\x00":
-            raise EngineError("Magic zero bytes absent [{:02x},{:02x}]".\
-                format(data[off], data[off + 1]))
+            raise EngineError("Magic zero bytes absent or mismatch")
         off += 2
 
         if len(data) - off > 0:


Commit: b2342c929b27c0add6dbc4d1ddf2a1a15a47c651
    https://github.com/scummvm/scummvm-tools/commit/b2342c929b27c0add6dbc4d1ddf2a1a15a47c651
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Switchable canvas and frame

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index f20876482..60c04df9e 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -18,7 +18,12 @@ class App(tkinter.Frame):
         self.pack(fill = tkinter.BOTH, expand = 1)
         self.pad = None
         self.sim = None
-        self.curr_mode = 0 
+        # gui
+        self.curr_mode = 0
+        self.curr_gui = []
+        self.curr_main = 0 # 0 - frame, 1 - canvas
+        self.last_main = -1
+        # canvas
         self.curr_width = 0
         self.curr_height = 0
         self.last_width = 1
@@ -27,7 +32,6 @@ class App(tkinter.Frame):
         self.canv_view_w = 0
         self.canv_view_h = 0
         self.canv_view_fact = 1
-        self.curr_gui = []
         self.main_image = tkinter.PhotoImage(width = 1, height = 1)
         self.after_idle(self.on_first_display)
         
@@ -60,14 +64,14 @@ class App(tkinter.Frame):
             scrollregion = (0, 0, 50, 50),
             xscrollcommand = self.scr_view_x.set,
             yscrollcommand = self.scr_view_y.set)
-        self.canv_view.grid(row = 0, column = 0, \
-            sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
         self.scr_view_x.config(command = self.canv_view.xview)
         self.scr_view_y.config(command = self.canv_view.yview)
         # don't forget
         #   canvas.config(scrollregion=(left, top, right, bottom))
         self.canv_view.bind('<Configure>', self.on_resize_view)
         self.canv_view.bind('<ButtonPress-1>', self.on_mouse_view)
+        # info panel
+        self.frm_info = ttk.Frame(self.frm_view)
 
         self.update_after()
         self.update_gui()
@@ -134,15 +138,12 @@ class App(tkinter.Frame):
 
     def update_canvas(self):
         # rebuild image
+        if self.curr_main != 1:
+            return
         c = self.canv_view
         c.delete(tkinter.ALL)
-
         if self.sim is None: return
-                    
-        #if (self.last_width != self.curr_width) or \
-        #   (self.last_height != self.curr_height):
-        #       self.build_image()
-        
+       
         # Preview image        
         #print("Update %d x %d" % (self.currWidth, self.currHeight))
         self.canv_image = self.main_image.copy()
@@ -256,7 +257,21 @@ class App(tkinter.Frame):
         return lb
 
     def update_gui(self):
-        # TODO: remove unused gui items
+        if self.last_main != self.curr_main:
+            if self.last_main == 0:
+                self.frm_info.grid_forget()
+            elif self.curr_main == 1:
+                self.canv_view.grid_forget()
+            self.last_main = self.curr_main
+            if self.curr_main == 0:
+                self.frm_info.grid(row = 0, column = 0, \
+                    sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
+                pass
+            elif self.curr_main == 1:
+                self.canv_view.grid(row = 0, column = 0, \
+                    sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
+            
+    
         for item in self.curr_gui:
             item()
         
@@ -266,6 +281,11 @@ class App(tkinter.Frame):
             for part in self.sim.parts:
                 lb.insert(tkinter.END, part)
             self.curr_lb = lb
+            
+            lab = ttk.Label(self.frm_info, text = "Select parts")
+            lab.pack()
+            
+            
         elif self.curr_mode == 100:
             # list resources
             lb = self.update_gui_add_left_listbox("Resources")   
@@ -318,10 +338,12 @@ class App(tkinter.Frame):
         
     def on_list_parts(self):
         self.curr_mode = 90
+        self.curr_main = 0
         self.update_gui()
 
     def on_list_res(self):
         self.curr_mode = 100
+        self.curr_main = 1
         self.update_gui()
         
     def open_data_from(self, folder):


Commit: acea4a19eb16db4977547cbbe42843504aa414da
    https://github.com/scummvm/scummvm-tools/commit/acea4a19eb16db4977547cbbe42843504aa414da
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Siwtchable interface works, outline mode

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 60c04df9e..683682846 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -23,14 +23,11 @@ class App(tkinter.Frame):
         self.curr_gui = []
         self.curr_main = 0 # 0 - frame, 1 - canvas
         self.last_main = -1
+        self.curr_lb_acts = None
         # canvas
         self.curr_width = 0
         self.curr_height = 0
-        self.last_width = 1
-        self.last_height = 1
         self.need_update = False
-        self.canv_view_w = 0
-        self.canv_view_h = 0
         self.canv_view_fact = 1
         self.main_image = tkinter.PhotoImage(width = 1, height = 1)
         self.after_idle(self.on_first_display)
@@ -64,15 +61,15 @@ class App(tkinter.Frame):
             scrollregion = (0, 0, 50, 50),
             xscrollcommand = self.scr_view_x.set,
             yscrollcommand = self.scr_view_y.set)
+        self.canv_view.grid(row = 0, column = 0, \
+            sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
         self.scr_view_x.config(command = self.canv_view.xview)
         self.scr_view_y.config(command = self.canv_view.yview)
         # don't forget
         #   canvas.config(scrollregion=(left, top, right, bottom))
         self.canv_view.bind('<Configure>', self.on_resize_view)
         self.canv_view.bind('<ButtonPress-1>', self.on_mouse_view)
-        # info panel
-        self.frm_info = ttk.Frame(self.frm_view)
-
+        
         self.update_after()
         self.update_gui()
 
@@ -94,6 +91,10 @@ class App(tkinter.Frame):
         self.menuedit = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menuedit,
                 label = "Edit")
+        self.menuedit.add_command(
+                command = self.on_outline,
+                label = "Outline")
+        self.menuedit.add_separator()
         self.menuedit.add_command(
                 command = self.on_list_parts,
                 label = "Select part")
@@ -127,29 +128,39 @@ class App(tkinter.Frame):
         #self.currMode += 1
         #if self.currMode > 1:
         #    self.currMode = 0
-        self.last_width = -1
-        self.last_height = -1
         self.update_after()
         
     def on_resize_view(self, event):
-        self.canv_view_w = event.width
-        self.canv_view_h = event.height
+        if self.curr_main != 1:          
+            w = self.frm_info.winfo_reqwidth()
+            h = self.frm_info.winfo_reqheight()
+            if w != self.canv_view.winfo_width() or \
+                    h != self.canv_view.winfo_height():
+                self.canv_view.itemconfigure(self.frm_info_id, 
+                    width = w, height = h)
+            return
         self.update_after()
 
+    def on_resize_info(self, event):
+        w = self.frm_info.winfo_reqwidth()
+        h = self.frm_info.winfo_reqheight()
+        self.canv_view.config(scrollregion = (0, 0, w, h))
+        if w != self.canv_view.winfo_width() or \
+                h != self.canv_view.winfo_height():
+            self.canv_view.config(width = w, height = h)
+
     def update_canvas(self):
-        # rebuild image
-        if self.curr_main != 1:
+        if self.curr_main != 1:          
             return
+        # draw grahics
         c = self.canv_view
         c.delete(tkinter.ALL)
         if self.sim is None: return
-       
         # Preview image        
-        #print("Update %d x %d" % (self.currWidth, self.currHeight))
         self.canv_image = self.main_image.copy()
-        w = self.canv_view_w
-        h = self.canv_view_h
-        if (w == 0) and (h == 0): 
+        w = self.canv_view.winfo_width() 
+        h = self.canv_view.winfo_height()
+        if (w == 0) or (h == 0): 
             return
         
         scale = 0 #self.RadioGroupScale.get()
@@ -182,7 +193,6 @@ class App(tkinter.Frame):
 
         cw = max(pw, w)
         ch = max(ph, h)
-    
         c.config(scrollregion = (0, 0, cw - 2, ch - 2))
     
         if fact > 0:
@@ -192,16 +202,7 @@ class App(tkinter.Frame):
         self.canv_image_fact = fact
         #print("Place c %d %d, p %d %d" % (cw, ch, w, h))
         c.create_image(cw // 2, ch // 2, image = self.canv_image)
-
-    def build_image(self):
-        # rebuild main_image
-        width = self.curr_width
-        height = self.curr_height
-        self.last_width = width
-        self.last_height = height
-
-        return
-        
+       
     def make_image(self, width, height, data):
         # create P6
         phdr = ("P6\n{} {}\n255\n".format(width, height))
@@ -230,7 +231,7 @@ class App(tkinter.Frame):
             data = bytes(p))
         return image                
 
-    def update_gui_add_left_listbox(self, text):
+    def update_gui_add_left_listbox(self, text, acts = None):
         lab = tkinter.Label(self.frm_left, text = text)
         lab.pack()
         
@@ -254,28 +255,52 @@ class App(tkinter.Frame):
         self.curr_gui.append(lambda:frm_lb.pack_forget())
         lb.bind("<Double-Button-1>", self.on_left_listbox)
         lb.bind("<Return>", self.on_left_listbox)
+
+        self.curr_lb_acts = acts
+        if acts:
+            for name, cb in acts:
+                lb.insert(tkinter.END, name)
+        self.curr_lb = lb
         return lb
 
     def update_gui(self):
         if self.last_main != self.curr_main:
-            if self.last_main == 0:
-                self.frm_info.grid_forget()
-            elif self.curr_main == 1:
-                self.canv_view.grid_forget()
             self.last_main = self.curr_main
+            self.canv_view.delete(tkinter.ALL)
             if self.curr_main == 0:
-                self.frm_info.grid(row = 0, column = 0, \
-                    sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
-                pass
-            elif self.curr_main == 1:
-                self.canv_view.grid(row = 0, column = 0, \
-                    sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
-            
+                # info panel
+                self.frm_info = ttk.Frame(self.canv_view)
+                self.frm_info_id = self.canv_view.create_window(0, 0, 
+                    window = self.frm_info, anchor = tkinter.NW)
+                self.frm_info.bind('<Configure>', self.on_resize_info)
+            self.update_canvas()
     
         for item in self.curr_gui:
             item()
+
+        if self.curr_mode == 0:
+            if self.sim is None:
+                # open some data
+                acts = [
+                    ("Open data", self.on_open_data)
+                ]
+                lb = self.update_gui_add_left_listbox("Outline", acts)                
+                lab = ttk.Label(self.frm_info, \
+                    text = "Open data")
+                lab.pack()
+                self.curr_gui.append(lambda:lab.pack_forget())                
+            else:
+                acts = [
+                    ("Parts", self.on_list_parts),
+                    ("Resources", self.on_list_res),
+                ]
+                lb = self.update_gui_add_left_listbox("Outline", acts)                
+                lab = ttk.Label(self.frm_info, \
+                    text = "Select data type from outline")
+                lab.pack()
+                self.curr_gui.append(lambda:lab.pack_forget())
         
-        if self.curr_mode == 90:
+        elif self.curr_mode == 90:
             # list parts
             lb = self.update_gui_add_left_listbox("Parts")   
             for part in self.sim.parts:
@@ -284,8 +309,8 @@ class App(tkinter.Frame):
             
             lab = ttk.Label(self.frm_info, text = "Select parts")
             lab.pack()
-            
-            
+            self.curr_gui.append(lambda:lab.pack_forget())
+
         elif self.curr_mode == 100:
             # list resources
             lb = self.update_gui_add_left_listbox("Resources")   
@@ -295,7 +320,16 @@ class App(tkinter.Frame):
             self.curr_lb = lb
 
     def on_left_listbox(self, event):
-        if self.curr_mode == 90:
+        if self.curr_lb_acts:
+            try:
+                num = self.curr_lb.curselection()[0]
+                num = int(num)
+            except:
+                pass
+            act = self.curr_lb_acts[num]
+            if act[1]:
+                act[1]()
+        elif self.curr_mode == 90:
             # parts
             try:
                 part_id = self.curr_lb.curselection()[0]
@@ -336,6 +370,11 @@ class App(tkinter.Frame):
         # open data - select TODO
         pass
         
+    def on_outline(self):
+        self.curr_mode = 0
+        self.curr_main = 0
+        self.update_gui()
+        
     def on_list_parts(self):
         self.curr_mode = 90
         self.curr_main = 0
@@ -350,10 +389,6 @@ class App(tkinter.Frame):
         self.sim = petka.Engine()
         self.sim.load_data(folder, "cp1251")
         self.sim.open_part(0, 0)
-        self.curr_mode = 90
-        #self.sim.open_part(1, 0)
-        #self.sim.open_part(2, 0)
-        #self.sim.open_part(3, 0)
 
 def main():
     root = tkinter.Tk()
@@ -363,15 +398,8 @@ def main():
     else:
         fn = "."
     app.open_data_from(fn)
-    
     app.mainloop()
-    #fman = petka.FileManager(".")
-    #fman.load_store("patch.str")
-    #fman.load_store("main.str")
-    #for k, v in fman.strtable.items():
-    #    print(k, "=", v)
-    # cleanup
-    #fman.unload_stores()
+
     
 if __name__ == "__main__":
     main()


Commit: cdb9edbb3632b6cfcc9dabd2ff701d4b1bdfd688
    https://github.com/scummvm/scummvm-tools/commit/cdb9edbb3632b6cfcc9dabd2ff701d4b1bdfd688
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Info panel fixes (not yet finished)

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 683682846..55f391013 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -102,6 +102,12 @@ class App(tkinter.Frame):
         self.menuedit.add_command(
                 command = self.on_list_res,
                 label = "Resources")
+        self.menuedit.add_command(
+                command = self.on_list_objs,
+                label = "Objects")
+        self.menuedit.add_command(
+                command = self.on_list_scenes,
+                label = "Scenes")
 
     def update_after(self):
         if not self.need_update:
@@ -290,22 +296,46 @@ class App(tkinter.Frame):
                 lab.pack()
                 self.curr_gui.append(lambda:lab.pack_forget())                
             else:
+                def tst_img():
+                    self.curr_main = 1
+                    self.main_image = tkinter.PhotoImage(\
+                        file = "img/splash.gif")
+                    self.curr_width = self.main_image.width()
+                    self.curr_height = self.main_image.height()
+                    self.update_gui()
+                def tst_info():
+                    self.curr_mode = 99
+                    self.curr_main = 0
+                    self.update_gui()
                 acts = [
                     ("Parts", self.on_list_parts),
                     ("Resources", self.on_list_res),
+                    ("Objects", self.on_list_objs),
+                    ("Scenes", self.on_list_scenes),
+                    ("-", None),
+                    ("Test image", tst_img),
+                    ("Test info", tst_info),
                 ]
                 lb = self.update_gui_add_left_listbox("Outline", acts)                
                 lab = ttk.Label(self.frm_info, \
                     text = "Select data type from outline")
                 lab.pack()
                 self.curr_gui.append(lambda:lab.pack_forget())
-        
+        elif self.curr_mode == 99:
+            acts = [
+                ("<- Back", self.on_outline)
+            
+            ]
+            lb = self.update_gui_add_left_listbox("Test info", acts)                
+            lab = ttk.Label(self.frm_info, \
+                text = "Text placed into looooooooooong string")
+            lab.pack()
+            self.curr_gui.append(lambda:lab.pack_forget())
         elif self.curr_mode == 90:
             # list parts
             lb = self.update_gui_add_left_listbox("Parts")   
             for part in self.sim.parts:
                 lb.insert(tkinter.END, part)
-            self.curr_lb = lb
             
             lab = ttk.Label(self.frm_info, text = "Select parts")
             lab.pack()
@@ -317,7 +347,16 @@ class App(tkinter.Frame):
             for res_id in self.sim.resord:
                 lb.insert(tkinter.END, "{} - {}".format(res_id, \
                     self.sim.res[res_id]))
-            self.curr_lb = lb
+        elif self.curr_mode == 101:
+            # list objects
+            lb = self.update_gui_add_left_listbox("Objects")   
+            for obj in self.sim.objects:
+                lb.insert(tkinter.END, "{} - {}".format(obj.idx, obj.name))
+        elif self.curr_mode == 102:
+            # list scenes
+            lb = self.update_gui_add_left_listbox("Scenes")   
+            for scn in self.sim.scenes:
+                lb.insert(tkinter.END, "{} - {}".format(scn.idx, scn.name))
 
     def on_left_listbox(self, event):
         if self.curr_lb_acts:
@@ -384,6 +423,16 @@ class App(tkinter.Frame):
         self.curr_mode = 100
         self.curr_main = 1
         self.update_gui()
+
+    def on_list_objs(self):
+        self.curr_mode = 101
+        self.curr_main = 0
+        self.update_gui()
+
+    def on_list_scenes(self):
+        self.curr_mode = 102
+        self.curr_main = 0
+        self.update_gui()
         
     def open_data_from(self, folder):
         self.sim = petka.Engine()


Commit: 42806a53667fef6b0c4d47cc47d3dcfe3e7a2d0d
    https://github.com/scummvm/scummvm-tools/commit/42806a53667fef6b0c4d47cc47d3dcfe3e7a2d0d
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Switchable interface works

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 55f391013..b5ff3553a 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -22,7 +22,6 @@ class App(tkinter.Frame):
         self.curr_mode = 0
         self.curr_gui = []
         self.curr_main = 0 # 0 - frame, 1 - canvas
-        self.last_main = -1
         self.curr_lb_acts = None
         # canvas
         self.curr_width = 0
@@ -36,6 +35,7 @@ class App(tkinter.Frame):
         
         ttk.Style().configure("Tool.TButton", width = -1) # minimal width
         ttk.Style().configure("TLabel", padding = self.pad)
+        ttk.Style().configure('Info.TFrame', background = 'white')
         
         # main paned
         self.pan_main = ttk.PanedWindow(self, orient = tkinter.HORIZONTAL)
@@ -58,6 +58,7 @@ class App(tkinter.Frame):
         self.scr_view_y.grid(row = 0, column = 1, sticky = \
             tkinter.N + tkinter.S)
         self.canv_view = tkinter.Canvas(self.frm_view, height = 150, 
+            bd = 0, highlightthickness = 0, bg = "white",
             scrollregion = (0, 0, 50, 50),
             xscrollcommand = self.scr_view_x.set,
             yscrollcommand = self.scr_view_y.set)
@@ -137,14 +138,13 @@ class App(tkinter.Frame):
         self.update_after()
         
     def on_resize_view(self, event):
-        if self.curr_main != 1:          
+        if self.curr_main == 0:          
             w = self.frm_info.winfo_reqwidth()
             h = self.frm_info.winfo_reqheight()
             if w != self.canv_view.winfo_width() or \
                     h != self.canv_view.winfo_height():
                 self.canv_view.itemconfigure(self.frm_info_id, 
                     width = w, height = h)
-            return
         self.update_after()
 
     def on_resize_info(self, event):
@@ -154,9 +154,10 @@ class App(tkinter.Frame):
         if w != self.canv_view.winfo_width() or \
                 h != self.canv_view.winfo_height():
             self.canv_view.config(width = w, height = h)
+        self.update_after()
 
     def update_canvas(self):
-        if self.curr_main != 1:          
+        if self.curr_main == 0:          
             return
         # draw grahics
         c = self.canv_view
@@ -268,21 +269,27 @@ class App(tkinter.Frame):
                 lb.insert(tkinter.END, name)
         self.curr_lb = lb
         return lb
+    
+    def update_gui_add_label(self, text):
+        lab = ttk.Label(self.frm_info, text = text)
+        lab.pack()
+        self.curr_gui.append(lambda:lab.pack_forget())                
 
     def update_gui(self):
-        if self.last_main != self.curr_main:
-            self.last_main = self.curr_main
-            self.canv_view.delete(tkinter.ALL)
-            if self.curr_main == 0:
-                # info panel
-                self.frm_info = ttk.Frame(self.canv_view)
-                self.frm_info_id = self.canv_view.create_window(0, 0, 
-                    window = self.frm_info, anchor = tkinter.NW)
-                self.frm_info.bind('<Configure>', self.on_resize_info)
-            self.update_canvas()
+        self.canv_view.delete(tkinter.ALL)
+        if self.curr_main == 0:
+            # info panel
+            self.frm_info = ttk.Frame(self.canv_view)
+            self.frm_info_id = self.canv_view.create_window(0, 0, 
+                window = self.frm_info, anchor = tkinter.NW)
+            self.frm_info.bind('<Configure>', self.on_resize_info)
+
+            self.canv_view.xview_moveto(0)
+            self.canv_view.yview_moveto(0)
     
         for item in self.curr_gui:
             item()
+        self.curr_gui = []
 
         if self.curr_mode == 0:
             if self.sim is None:
@@ -290,11 +297,8 @@ class App(tkinter.Frame):
                 acts = [
                     ("Open data", self.on_open_data)
                 ]
-                lb = self.update_gui_add_left_listbox("Outline", acts)                
-                lab = ttk.Label(self.frm_info, \
-                    text = "Open data")
-                lab.pack()
-                self.curr_gui.append(lambda:lab.pack_forget())                
+                self.update_gui_add_left_listbox("Outline", acts)                
+                self.update_gui_add_label("Open data")
             else:
                 def tst_img():
                     self.curr_main = 1
@@ -316,31 +320,20 @@ class App(tkinter.Frame):
                     ("Test image", tst_img),
                     ("Test info", tst_info),
                 ]
-                lb = self.update_gui_add_left_listbox("Outline", acts)                
-                lab = ttk.Label(self.frm_info, \
-                    text = "Select data type from outline")
-                lab.pack()
-                self.curr_gui.append(lambda:lab.pack_forget())
+                self.update_gui_add_left_listbox("Outline", acts)                
+                self.update_gui_add_label("Select data type from outline")
         elif self.curr_mode == 99:
             acts = [
                 ("<- Back", self.on_outline)
-            
             ]
-            lb = self.update_gui_add_left_listbox("Test info", acts)                
-            lab = ttk.Label(self.frm_info, \
-                text = "Text placed into looooooooooong string")
-            lab.pack()
-            self.curr_gui.append(lambda:lab.pack_forget())
+            self.update_gui_add_left_listbox("Test info", acts)                
+            self.update_gui_add_label("Text placed into looooooooooong string")
         elif self.curr_mode == 90:
             # list parts
             lb = self.update_gui_add_left_listbox("Parts")   
             for part in self.sim.parts:
                 lb.insert(tkinter.END, part)
-            
-            lab = ttk.Label(self.frm_info, text = "Select parts")
-            lab.pack()
-            self.curr_gui.append(lambda:lab.pack_forget())
-
+            self.update_gui_add_label("Select parts")
         elif self.curr_mode == 100:
             # list resources
             lb = self.update_gui_add_left_listbox("Resources")   
@@ -352,11 +345,15 @@ class App(tkinter.Frame):
             lb = self.update_gui_add_left_listbox("Objects")   
             for obj in self.sim.objects:
                 lb.insert(tkinter.END, "{} - {}".format(obj.idx, obj.name))
+            self.update_gui_add_label("Object info")
         elif self.curr_mode == 102:
             # list scenes
             lb = self.update_gui_add_left_listbox("Scenes")   
             for scn in self.sim.scenes:
                 lb.insert(tkinter.END, "{} - {}".format(scn.idx, scn.name))
+            self.update_gui_add_label("Scene info")
+
+        self.update_after()
 
     def on_left_listbox(self, event):
         if self.curr_lb_acts:


Commit: 86a8ced5ac4fc2d48fc77ae00ea8d92f96f97613
    https://github.com/scummvm/scummvm-tools/commit/86a8ced5ac4fc2d48fc77ae00ea8d92f96f97613
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Fix unloading stores

Changed paths:
    engines/petka/petka/fman.py


diff --git a/engines/petka/petka/fman.py b/engines/petka/petka/fman.py
index f61d5ed7c..3fee9123e 100644
--- a/engines/petka/petka/fman.py
+++ b/engines/petka/petka/fman.py
@@ -30,8 +30,6 @@ class FileManager:
                 break
             if not ok: return None
         return npath
-        
-        
     
     def load_store(self, name, tag = 0):
         path = self.find_path(name)
@@ -61,8 +59,8 @@ class FileManager:
         for idx, fname in enumerate(data.split("\x00")):
             fname = fname.lower().replace("\\", "/")
             if idx < index_len and fname not in self.strtable:
-                self.strtable[fname] = (len(self.strfd),) + \
-                    index_table[idx]
+                self.strtable[fname] = (len(self.strfd),) + index_table[idx]
+                print("STORE:", fname)
             else:
                 if len(fname) > 0:
                     raise EngineError("Extra file record \"{}\" in \"{}\"".\
@@ -97,10 +95,10 @@ class FileManager:
         strtable = {}
         for idx, (fd, name, tag) in enumerate(self.strfd):
             if flt is not None:
-                if tag != flt: 
-                    for k, v in self.strtable:
-                        if v == idx:
-                            strtable[k] = len(strfd)
+                if tag != flt:
+                    for k, v in self.strtable.items():
+                        if v[0] == idx:
+                            strtable[k] = (len(strfd), v[1], v[2])
                     strfd.append((fd, name, tag))
                     continue
             print("DEBUG: Unload store \"{}\"".format(name))
@@ -108,6 +106,6 @@ class FileManager:
                 if fd: fd.close()
             except Exception as e:
                 print("DEBUG: Can't unload \"{}\":".format(name) + str(e))
-        self.strff = strfd
+        self.strfd = strfd
         self.strtable = strtable
         


Commit: 8f81a2b9148305cfaf49240583e78c1635aca701
    https://github.com/scummvm/scummvm-tools/commit/8f81a2b9148305cfaf49240583e78c1635aca701
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Refine outline information

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py
    engines/petka/petka/fman.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index b5ff3553a..78be2752c 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -35,7 +35,7 @@ class App(tkinter.Frame):
         
         ttk.Style().configure("Tool.TButton", width = -1) # minimal width
         ttk.Style().configure("TLabel", padding = self.pad)
-        ttk.Style().configure('Info.TFrame', background = 'white')
+        ttk.Style().configure('Info.TFrame', background = 'white', foreground = "black")
         
         # main paned
         self.pan_main = ttk.PanedWindow(self, orient = tkinter.HORIZONTAL)
@@ -58,7 +58,7 @@ class App(tkinter.Frame):
         self.scr_view_y.grid(row = 0, column = 1, sticky = \
             tkinter.N + tkinter.S)
         self.canv_view = tkinter.Canvas(self.frm_view, height = 150, 
-            bd = 0, highlightthickness = 0, bg = "white",
+            bd = 0, highlightthickness = 0, 
             scrollregion = (0, 0, 50, 50),
             xscrollcommand = self.scr_view_x.set,
             yscrollcommand = self.scr_view_y.set)
@@ -313,18 +313,19 @@ class App(tkinter.Frame):
                     self.update_gui()
                 acts = [
                     ("Parts", self.on_list_parts),
-                    ("Resources", self.on_list_res),
-                    ("Objects", self.on_list_objs),
-                    ("Scenes", self.on_list_scenes),
+                    ("Resources ({})".format(len(self.sim.res)), self.on_list_res),
+                    ("Objects ({})".format(len(self.sim.objects)), self.on_list_objs),
+                    ("Scenes ({})".format(len(self.sim.scenes)), self.on_list_scenes),
                     ("-", None),
                     ("Test image", tst_img),
                     ("Test info", tst_info),
                 ]
-                self.update_gui_add_left_listbox("Outline", acts)                
+                self.update_gui_add_left_listbox("Outline: part {} chapter {}".\
+                    format(self.sim.curr_part, self.sim.curr_chap), acts)                
                 self.update_gui_add_label("Select data type from outline")
         elif self.curr_mode == 99:
             acts = [
-                ("<- Back", self.on_outline)
+                ("<- outline", self.on_outline)
             ]
             self.update_gui_add_left_listbox("Test info", acts)                
             self.update_gui_add_label("Text placed into looooooooooong string")
@@ -334,6 +335,8 @@ class App(tkinter.Frame):
             for part in self.sim.parts:
                 lb.insert(tkinter.END, part)
             self.update_gui_add_label("Select parts")
+            self.update_gui_add_label("or goto outline")
+            
         elif self.curr_mode == 100:
             # list resources
             lb = self.update_gui_add_left_listbox("Resources")   
@@ -378,7 +381,7 @@ class App(tkinter.Frame):
             cnum = pnum.split("Chapter", 1)
             if len(cnum) > 1:
                 pnum = int(cnum[0].strip(), 10)
-                cnum = int(cnum[0].strip(), 10)
+                cnum = int(cnum[1].strip(), 10)
             else:
                 cnum = 0
             self.sim.open_part(pnum, cnum)
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 48955e95b..b02960844 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -82,9 +82,9 @@ class Engine:
                 data = self.parts_ini[sect]
                 if sect == "All":
                     if "Part" in data:
-                        self.start_part = int(data["Part"]) - 1
+                        self.start_part = int(data["Part"])
                     if "Chapter" in data:
-                        self.start_chap = int(data["Chapter"]) - 1
+                        self.start_chap = int(data["Chapter"])
                 elif sect[:5] == "Part ":
                     self.parts.append(sect)
         else:
@@ -97,6 +97,8 @@ class Engine:
 
     def open_part(self, part, chap):
         self.fman.unload_stores(1)
+        self.curr_part = part
+        self.curr_chap = chap
         if self.parts_ini:
             pname = "Part {}".format(part)
             pcname = pname
@@ -106,18 +108,19 @@ class Engine:
             self.curr_path = ini["CurrentPath"]
             self.curr_speech = ini["PathSpeech"]
             self.curr_diskid = ini["DiskID"]
+            inic = self.parts_ini[pcname]
+            if "Chapter" in inic:
+                self.fman.load_store(inic["Chapter"], 1)
         else:
             ini = {}
             self.curr_path = ""
             self.curr_speech = ""
             self.curr_diskid = None
-
         
         # load BGS.INI
         self.bgs_ini = {}
         self.start_scene = None
         pf = self.fman.find_path(self.curr_path + "bgs.ini")
-        print(self.curr_path)
         if pf:
             f = open(pf, "rb")
             try:
diff --git a/engines/petka/petka/fman.py b/engines/petka/petka/fman.py
index 3fee9123e..12435df8a 100644
--- a/engines/petka/petka/fman.py
+++ b/engines/petka/petka/fman.py
@@ -60,7 +60,6 @@ class FileManager:
             fname = fname.lower().replace("\\", "/")
             if idx < index_len and fname not in self.strtable:
                 self.strtable[fname] = (len(self.strfd),) + index_table[idx]
-                print("STORE:", fname)
             else:
                 if len(fname) > 0:
                     raise EngineError("Extra file record \"{}\" in \"{}\"".\


Commit: dd01092a27d2e408a9dabdd760d631a02777dc2c
    https://github.com/scummvm/scummvm-tools/commit/dd01092a27d2e408a9dabdd760d631a02777dc2c
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Refine outline information

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 78be2752c..276c42afc 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -11,6 +11,38 @@ import petka
 
 APPNAME = "P1&2 Explorer"
 
+# thanx to http://effbot.org/zone/tkinter-text-hyperlink.htm
+class HyperlinkManager:
+    def __init__(self, text):
+	    self.text = text
+	    self.text.tag_config("hyper", foreground = "blue", underline = 1)
+	    self.text.tag_bind("hyper", "<Enter>", self._enter)
+	    self.text.tag_bind("hyper", "<Leave>", self._leave)
+	    self.text.tag_bind("hyper", "<Button-1>", self._click)
+	    self.reset()
+
+    def reset(self):
+    	self.links = {}
+
+    def add(self, action):
+        # add an action to the manager.  returns tags to use in
+        # associated text widget
+	    tag = "hyper-{}".format(len(self.links))
+	    self.links[tag] = action
+	    return "hyper", tag
+
+    def _enter(self, event):
+    	self.text.config(cursor = "hand2")
+
+    def _leave(self, event):
+    	self.text.config(cursor = "")
+
+    def _click(self, event):
+	    for tag in self.text.tag_names(CURRENT):
+	        if tag[:6] == "hyper-":
+		        self.links[tag]()
+    		    return
+		
 class App(tkinter.Frame):
     def __init__(self, master):
         tkinter.Frame.__init__(self, master)


Commit: f7dfc9c6f1dab6be930eded0445e15f9b2b3abbf
    https://github.com/scummvm/scummvm-tools/commit/f7dfc9c6f1dab6be930eded0445e15f9b2b3abbf
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Hyperlinks in info panel

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 276c42afc..dd3f22dba 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -14,34 +14,33 @@ APPNAME = "P1&2 Explorer"
 # thanx to http://effbot.org/zone/tkinter-text-hyperlink.htm
 class HyperlinkManager:
     def __init__(self, text):
-	    self.text = text
-	    self.text.tag_config("hyper", foreground = "blue", underline = 1)
-	    self.text.tag_bind("hyper", "<Enter>", self._enter)
-	    self.text.tag_bind("hyper", "<Leave>", self._leave)
-	    self.text.tag_bind("hyper", "<Button-1>", self._click)
-	    self.reset()
-
+        self.text = text
+        self.text.tag_config("hyper", foreground = "blue", underline = 1)
+        self.text.tag_bind("hyper", "<Enter>", self._enter)
+        self.text.tag_bind("hyper", "<Leave>", self._leave)
+        self.text.tag_bind("hyper", "<Button-1>", self._click)
+        self.reset()
     def reset(self):
     	self.links = {}
 
     def add(self, action):
         # add an action to the manager.  returns tags to use in
         # associated text widget
-	    tag = "hyper-{}".format(len(self.links))
-	    self.links[tag] = action
-	    return "hyper", tag
+        tag = "hyper-{}".format(len(self.links))
+        self.links[tag] = action
+        return "hyper", tag
 
     def _enter(self, event):
-    	self.text.config(cursor = "hand2")
+        self.text.config(cursor = "hand2")
 
     def _leave(self, event):
-    	self.text.config(cursor = "")
+        self.text.config(cursor = "")
 
     def _click(self, event):
-	    for tag in self.text.tag_names(CURRENT):
-	        if tag[:6] == "hyper-":
-		        self.links[tag]()
-    		    return
+        for tag in self.text.tag_names(tkinter.CURRENT):
+            if tag[:6] == "hyper-":
+                self.links[tag]()
+                return
 		
 class App(tkinter.Frame):
     def __init__(self, master):
@@ -309,19 +308,23 @@ class App(tkinter.Frame):
 
     def update_gui(self):
         self.canv_view.delete(tkinter.ALL)
+        for item in self.curr_gui:
+            item()
+        self.curr_gui = []
+
         if self.curr_main == 0:
             # info panel
             self.frm_info = ttk.Frame(self.canv_view)
             self.frm_info_id = self.canv_view.create_window(0, 0, 
                 window = self.frm_info, anchor = tkinter.NW)
             self.frm_info.bind('<Configure>', self.on_resize_info)
-
             self.canv_view.xview_moveto(0)
             self.canv_view.yview_moveto(0)
-    
-        for item in self.curr_gui:
-            item()
-        self.curr_gui = []
+            # info data
+            self.frm_info_text = tkinter.Text(self.frm_info)
+            self.frm_info_hl = HyperlinkManager(self.frm_info_text)
+            self.frm_info_text.pack(expand = 1, fill = tkinter.BOTH)
+            self.curr_gui.append(lambda:self.frm_info_text.pack_forget())
 
         if self.curr_mode == 0:
             if self.sim is None:
@@ -366,9 +369,6 @@ class App(tkinter.Frame):
             lb = self.update_gui_add_left_listbox("Parts")   
             for part in self.sim.parts:
                 lb.insert(tkinter.END, part)
-            self.update_gui_add_label("Select parts")
-            self.update_gui_add_label("or goto outline")
-            
         elif self.curr_mode == 100:
             # list resources
             lb = self.update_gui_add_left_listbox("Resources")   
@@ -387,9 +387,40 @@ class App(tkinter.Frame):
             for scn in self.sim.scenes:
                 lb.insert(tkinter.END, "{} - {}".format(scn.idx, scn.name))
             self.update_gui_add_label("Scene info")
-
+        self.update_info()
         self.update_after()
 
+    def update_info(self):
+        def stdinfo():
+            self.frm_info_text.delete(0.0, tkinter.END)
+            self.frm_info_text.insert(tkinter.INSERT, "<- Outline", \
+                self.frm_info_hl.add(self.on_outline))
+            self.frm_info_text.insert(tkinter.INSERT, "\n\n")
+        if self.curr_mode == 99:
+            stdinfo()
+            for i in range(100):
+                self.frm_info_text.insert(tkinter.INSERT, "Item {}\n".format(i))
+        elif self.curr_mode == 90:
+            stdinfo()
+            self.frm_info_text.insert(tkinter.INSERT, \
+                "Current: part {} chapter {}\n\n  Resources: ".\
+                    format(self.sim.curr_part, self.sim.curr_chap))
+            self.frm_info_text.insert(tkinter.INSERT, "{}".\
+                format(len(self.sim.res)), \
+                self.frm_info_hl.add(self.on_list_res))
+            self.frm_info_text.insert(tkinter.INSERT, "\n  Objects:   ")
+            self.frm_info_text.insert(tkinter.INSERT, "{}".\
+                format(len(self.sim.objects)), \
+                self.frm_info_hl.add(self.on_list_objs))
+            self.frm_info_text.insert(tkinter.INSERT, "\n  Scenes:    ")
+            self.frm_info_text.insert(tkinter.INSERT, "{}".\
+                format(len(self.sim.scenes)), \
+                self.frm_info_hl.add(self.on_list_scenes))
+        elif self.curr_mode == 101:
+            stdinfo()
+        elif self.curr_mode == 102:
+            stdinfo()
+
     def on_left_listbox(self, event):
         if self.curr_lb_acts:
             try:
@@ -417,6 +448,7 @@ class App(tkinter.Frame):
             else:
                 cnum = 0
             self.sim.open_part(pnum, cnum)
+            self.update_info()
             self.update_after()
         elif self.curr_mode == 100:
             # resources


Commit: 8ed83d8ed40adb2f4587ce74afe58795bb4edcf9
    https://github.com/scummvm/scummvm-tools/commit/8ed83d8ed40adb2f4587ce74afe58795bb4edcf9
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Info panel works, refactor

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index dd3f22dba..20e8f95e7 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -75,8 +75,7 @@ class App(tkinter.Frame):
         # leftpanel
         self.frm_left = ttk.Frame(self.pan_main)
         self.pan_main.add(self.frm_left)
-
-        # canvas
+        # main view
         self.frm_view = ttk.Frame(self.pan_main)
         self.pan_main.add(self.frm_view)
         self.frm_view.grid_rowconfigure(0, weight = 1)
@@ -88,20 +87,24 @@ class App(tkinter.Frame):
         self.scr_view_y = ttk.Scrollbar(self.frm_view)
         self.scr_view_y.grid(row = 0, column = 1, sticky = \
             tkinter.N + tkinter.S)
+        # canvas
         self.canv_view = tkinter.Canvas(self.frm_view, height = 150, 
             bd = 0, highlightthickness = 0, 
             scrollregion = (0, 0, 50, 50),
-            xscrollcommand = self.scr_view_x.set,
-            yscrollcommand = self.scr_view_y.set)
-        self.canv_view.grid(row = 0, column = 0, \
-            sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
-        self.scr_view_x.config(command = self.canv_view.xview)
-        self.scr_view_y.config(command = self.canv_view.yview)
+            )
         # don't forget
         #   canvas.config(scrollregion=(left, top, right, bottom))
         self.canv_view.bind('<Configure>', self.on_resize_view)
         self.canv_view.bind('<ButtonPress-1>', self.on_mouse_view)
         
+        # text
+        self.text_view = tkinter.Text(self.frm_view,
+            highlightthickness = 0,
+            )
+        self.text_hl = HyperlinkManager(self.text_view)
+        self.text_view.bind('<Configure>', self.on_resize_view)
+        
+        
         self.update_after()
         self.update_gui()
 
@@ -169,22 +172,6 @@ class App(tkinter.Frame):
         self.update_after()
         
     def on_resize_view(self, event):
-        if self.curr_main == 0:          
-            w = self.frm_info.winfo_reqwidth()
-            h = self.frm_info.winfo_reqheight()
-            if w != self.canv_view.winfo_width() or \
-                    h != self.canv_view.winfo_height():
-                self.canv_view.itemconfigure(self.frm_info_id, 
-                    width = w, height = h)
-        self.update_after()
-
-    def on_resize_info(self, event):
-        w = self.frm_info.winfo_reqwidth()
-        h = self.frm_info.winfo_reqheight()
-        self.canv_view.config(scrollregion = (0, 0, w, h))
-        if w != self.canv_view.winfo_width() or \
-                h != self.canv_view.winfo_height():
-            self.canv_view.config(width = w, height = h)
         self.update_after()
 
     def update_canvas(self):
@@ -301,11 +288,6 @@ class App(tkinter.Frame):
         self.curr_lb = lb
         return lb
     
-    def update_gui_add_label(self, text):
-        lab = ttk.Label(self.frm_info, text = text)
-        lab.pack()
-        self.curr_gui.append(lambda:lab.pack_forget())                
-
     def update_gui(self):
         self.canv_view.delete(tkinter.ALL)
         for item in self.curr_gui:
@@ -313,18 +295,25 @@ class App(tkinter.Frame):
         self.curr_gui = []
 
         if self.curr_main == 0:
-            # info panel
-            self.frm_info = ttk.Frame(self.canv_view)
-            self.frm_info_id = self.canv_view.create_window(0, 0, 
-                window = self.frm_info, anchor = tkinter.NW)
-            self.frm_info.bind('<Configure>', self.on_resize_info)
-            self.canv_view.xview_moveto(0)
-            self.canv_view.yview_moveto(0)
-            # info data
-            self.frm_info_text = tkinter.Text(self.frm_info)
-            self.frm_info_hl = HyperlinkManager(self.frm_info_text)
-            self.frm_info_text.pack(expand = 1, fill = tkinter.BOTH)
-            self.curr_gui.append(lambda:self.frm_info_text.pack_forget())
+            self.canv_view.grid_forget()
+            self.text_view.grid(row = 0, column = 0, \
+                sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
+            self.text_view.configure(
+                xscrollcommand = self.scr_view_x.set,
+                yscrollcommand = self.scr_view_y.set
+            )
+            self.scr_view_x.config(command = self.text_view.xview)
+            self.scr_view_y.config(command = self.text_view.yview)
+        else:
+            self.text_view.grid_forget()
+            self.canv_view.grid(row = 0, column = 0, \
+                sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
+            self.canv_view.configure(
+                xscrollcommand = self.scr_view_x.set,
+                yscrollcommand = self.scr_view_y.set
+            )
+            self.scr_view_x.config(command = self.canv_view.xview)
+            self.scr_view_y.config(command = self.canv_view.yview)
 
         if self.curr_mode == 0:
             if self.sim is None:
@@ -333,7 +322,6 @@ class App(tkinter.Frame):
                     ("Open data", self.on_open_data)
                 ]
                 self.update_gui_add_left_listbox("Outline", acts)                
-                self.update_gui_add_label("Open data")
             else:
                 def tst_img():
                     self.curr_main = 1
@@ -357,13 +345,11 @@ class App(tkinter.Frame):
                 ]
                 self.update_gui_add_left_listbox("Outline: part {} chapter {}".\
                     format(self.sim.curr_part, self.sim.curr_chap), acts)                
-                self.update_gui_add_label("Select data type from outline")
         elif self.curr_mode == 99:
             acts = [
                 ("<- outline", self.on_outline)
             ]
             self.update_gui_add_left_listbox("Test info", acts)                
-            self.update_gui_add_label("Text placed into looooooooooong string")
         elif self.curr_mode == 90:
             # list parts
             lb = self.update_gui_add_left_listbox("Parts")   
@@ -380,55 +366,74 @@ class App(tkinter.Frame):
             lb = self.update_gui_add_left_listbox("Objects")   
             for obj in self.sim.objects:
                 lb.insert(tkinter.END, "{} - {}".format(obj.idx, obj.name))
-            self.update_gui_add_label("Object info")
         elif self.curr_mode == 102:
             # list scenes
             lb = self.update_gui_add_left_listbox("Scenes")   
             for scn in self.sim.scenes:
                 lb.insert(tkinter.END, "{} - {}".format(scn.idx, scn.name))
-            self.update_gui_add_label("Scene info")
         self.update_info()
         self.update_after()
 
     def update_info(self):
         def stdinfo():
-            self.frm_info_text.delete(0.0, tkinter.END)
-            self.frm_info_text.insert(tkinter.INSERT, "<- Outline", \
-                self.frm_info_hl.add(self.on_outline))
-            self.frm_info_text.insert(tkinter.INSERT, "\n\n")
-        if self.curr_mode == 99:
+            self.text_view.delete(0.0, tkinter.END)
+            self.text_view.insert(tkinter.INSERT, "<- Outline", \
+                self.text_hl.add(self.on_outline))
+            self.text_view.insert(tkinter.INSERT, "\n\n")
+           
+        if self.curr_mode == 0:
+            self.text_view.delete(0.0, tkinter.END)
+            if self.sim is None:
+                self.text_view.insert(tkinter.INSERT, "No data loaded")
+                self.text_view.insert(tkinter.INSERT, "Open data", \
+                    self.text_hl.add(self.on_open_data))
+            else:
+                self.text_view.insert(tkinter.INSERT, \
+                    "Select type from outline")
+        elif self.curr_mode == 99:
             stdinfo()
             for i in range(100):
-                self.frm_info_text.insert(tkinter.INSERT, "Item {}\n".format(i))
+                self.text_view.insert(tkinter.INSERT, \
+                    "Item {}\n".format(i))
         elif self.curr_mode == 90:
             stdinfo()
-            self.frm_info_text.insert(tkinter.INSERT, \
+            self.text_view.insert(tkinter.INSERT, \
                 "Current: part {} chapter {}\n\n  Resources: ".\
                     format(self.sim.curr_part, self.sim.curr_chap))
-            self.frm_info_text.insert(tkinter.INSERT, "{}".\
+            self.text_view.insert(tkinter.INSERT, "{}".\
                 format(len(self.sim.res)), \
-                self.frm_info_hl.add(self.on_list_res))
-            self.frm_info_text.insert(tkinter.INSERT, "\n  Objects:   ")
-            self.frm_info_text.insert(tkinter.INSERT, "{}".\
+                self.text_hl.add(self.on_list_res))
+            self.text_view.insert(tkinter.INSERT, "\n    Objects: ")
+            self.text_view.insert(tkinter.INSERT, "{}".\
                 format(len(self.sim.objects)), \
-                self.frm_info_hl.add(self.on_list_objs))
-            self.frm_info_text.insert(tkinter.INSERT, "\n  Scenes:    ")
-            self.frm_info_text.insert(tkinter.INSERT, "{}".\
+                self.text_hl.add(self.on_list_objs))
+            self.text_view.insert(tkinter.INSERT, "\n     Scenes: ")
+            self.text_view.insert(tkinter.INSERT, "{}".\
                 format(len(self.sim.scenes)), \
-                self.frm_info_hl.add(self.on_list_scenes))
+                self.text_hl.add(self.on_list_scenes))
         elif self.curr_mode == 101:
             stdinfo()
         elif self.curr_mode == 102:
             stdinfo()
+        else:
+            stdinfo()
 
     def on_left_listbox(self, event):
-        if self.curr_lb_acts:
+        def currsel():
             try:
                 num = self.curr_lb.curselection()[0]
                 num = int(num)
             except:
                 pass
-            act = self.curr_lb_acts[num]
+            return num
+
+        def objinfo(tp, rec):
+            self.text_view.insert(tkinter.INSERT, tp + "\n\n")
+            self.text_view.insert(tkinter.INSERT, \
+                "  Index: {}\n   Name: {}".format(rec.idx, rec.name))
+
+        if self.curr_lb_acts:
+            act = self.curr_lb_acts[currsel()]
             if act[1]:
                 act[1]()
         elif self.curr_mode == 90:
@@ -468,6 +473,14 @@ class App(tkinter.Frame):
                 self.curr_height = bmp.height
                 self.update_after()
             print(fn)
+        elif self.curr_mode == 101:
+            # objects
+            self.update_info()
+            objinfo("Object:", self.sim.objects[currsel()])
+        elif self.curr_mode == 102:
+            # scenes
+            self.update_info()
+            objinfo("Scene:", self.sim.scenes[currsel()])
 
     def on_open_data(self):
         # open data - select TODO


Commit: 56e1eaa0971d454ce3bf4189e1b48ab9e9854406
    https://github.com/scummvm/scummvm-tools/commit/56e1eaa0971d454ce3bf4189e1b48ab9e9854406
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Names and invntr information

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py
    engines/petka/petka/fman.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 20e8f95e7..0120f61b5 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -143,6 +143,12 @@ class App(tkinter.Frame):
         self.menuedit.add_command(
                 command = self.on_list_scenes,
                 label = "Scenes")
+        self.menuedit.add_command(
+                command = self.on_list_names,
+                label = "Names")
+        self.menuedit.add_command(
+                command = self.on_list_invntr,
+                label = "Invntr")
 
     def update_after(self):
         if not self.need_update:
@@ -336,9 +342,16 @@ class App(tkinter.Frame):
                     self.update_gui()
                 acts = [
                     ("Parts", self.on_list_parts),
-                    ("Resources ({})".format(len(self.sim.res)), self.on_list_res),
-                    ("Objects ({})".format(len(self.sim.objects)), self.on_list_objs),
-                    ("Scenes ({})".format(len(self.sim.scenes)), self.on_list_scenes),
+                    ("Resources ({})".format(len(self.sim.res)), \
+                        self.on_list_res),
+                    ("Objects ({})".format(len(self.sim.objects)), \
+                        self.on_list_objs),
+                    ("Scenes ({})".format(len(self.sim.scenes)), \
+                        self.on_list_scenes),
+                    ("Names ({})".format(len(self.sim.names)), \
+                        self.on_list_names),
+                    ("Invntr ({})".format(len(self.sim.invntr)), \
+                        self.on_list_invntr),
                     ("-", None),
                     ("Test image", tst_img),
                     ("Test info", tst_info),
@@ -371,6 +384,16 @@ class App(tkinter.Frame):
             lb = self.update_gui_add_left_listbox("Scenes")   
             for scn in self.sim.scenes:
                 lb.insert(tkinter.END, "{} - {}".format(scn.idx, scn.name))
+        elif self.curr_mode == 103:
+            # list names
+            lb = self.update_gui_add_left_listbox("Names")   
+            for name in self.sim.names.keys():
+                lb.insert(tkinter.END, "{}".format(name))
+        elif self.curr_mode == 104:
+            # list invntr
+            lb = self.update_gui_add_left_listbox("Invntr")   
+            for name in self.sim.invntr.keys():
+                lb.insert(tkinter.END, "{}".format(name))
         self.update_info()
         self.update_after()
 
@@ -403,14 +426,22 @@ class App(tkinter.Frame):
             self.text_view.insert(tkinter.INSERT, "{}".\
                 format(len(self.sim.res)), \
                 self.text_hl.add(self.on_list_res))
-            self.text_view.insert(tkinter.INSERT, "\n    Objects: ")
+            self.text_view.insert(tkinter.INSERT, "\n  Objects:   ")
             self.text_view.insert(tkinter.INSERT, "{}".\
                 format(len(self.sim.objects)), \
                 self.text_hl.add(self.on_list_objs))
-            self.text_view.insert(tkinter.INSERT, "\n     Scenes: ")
+            self.text_view.insert(tkinter.INSERT, "\n  Scenes:    ")
             self.text_view.insert(tkinter.INSERT, "{}".\
                 format(len(self.sim.scenes)), \
                 self.text_hl.add(self.on_list_scenes))
+            self.text_view.insert(tkinter.INSERT, "\n  Names:     ")
+            self.text_view.insert(tkinter.INSERT, "{}".\
+                format(len(self.sim.names)), \
+                self.text_hl.add(self.on_list_names))
+            self.text_view.insert(tkinter.INSERT, "\n  Invntr:    ")
+            self.text_view.insert(tkinter.INSERT, "{}".\
+                format(len(self.sim.invntr)), \
+                self.text_hl.add(self.on_list_invntr))
         elif self.curr_mode == 101:
             stdinfo()
         elif self.curr_mode == 102:
@@ -428,10 +459,17 @@ class App(tkinter.Frame):
             return num
 
         def objinfo(tp, rec):
-            self.text_view.insert(tkinter.INSERT, tp + "\n\n")
             self.text_view.insert(tkinter.INSERT, \
-                "  Index: {}\n   Name: {}".format(rec.idx, rec.name))
-
+                ("Object" if tp else "Scene") + ":\n")
+            self.text_view.insert(tkinter.INSERT, \
+                "  Index:  {}\n  Name:   {}\n".format(rec.idx, rec.name))
+            if rec.name in self.sim.names:
+                self.text_view.insert(tkinter.INSERT, \
+                    "  Alias:  {}\n".format(self.sim.names[rec.name]))
+            if rec.name in self.sim.invntr:
+                self.text_view.insert(tkinter.INSERT, \
+                    "  Invntr: {}\n".format(self.sim.invntr[rec.name]))
+                
         if self.curr_lb_acts:
             act = self.curr_lb_acts[currsel()]
             if act[1]:
@@ -476,11 +514,57 @@ class App(tkinter.Frame):
         elif self.curr_mode == 101:
             # objects
             self.update_info()
-            objinfo("Object:", self.sim.objects[currsel()])
+            objinfo(True, self.sim.objects[currsel()])
         elif self.curr_mode == 102:
             # scenes
             self.update_info()
-            objinfo("Scene:", self.sim.scenes[currsel()])
+            objinfo(False, self.sim.scenes[currsel()])
+        elif self.curr_mode == 103:
+            # names
+            self.update_info()
+            key = list(self.sim.names.keys())[currsel()]
+            self.text_view.insert(tkinter.INSERT, \
+                "Object: {}\n".format(key))
+            self.text_view.insert(tkinter.INSERT, \
+                "Alias:  {}\n\n".format(self.sim.names[key]))
+            # search for objects
+            self.text_view.insert(tkinter.INSERT, \
+                "Applied for:")
+            for obj in self.sim.objects:
+                if obj.name == key:
+                    self.text_view.insert(tkinter.INSERT, \
+                        "\n  ")
+                    def make_cb(idx):
+                        def cb():
+                            print("TODO: names obj", idx)
+                        return cb
+                    self.text_view.insert(tkinter.INSERT, \
+                        "{} - {}".format(obj.idx, obj.name), \
+                        self.text_hl.add(make_cb(obj.idx)))
+        elif self.curr_mode == 104:
+            # invntr
+            self.update_info()
+            key = list(self.sim.invntr.keys())[currsel()]
+            self.text_view.insert(tkinter.INSERT, \
+                "Object: {}\n".format(key))
+            self.text_view.insert(tkinter.INSERT, \
+                "{}\n\n".format(self.sim.invntr[key]))
+            # search for objects
+            self.text_view.insert(tkinter.INSERT, \
+                "Applied for:")
+            for obj in self.sim.objects:
+                if obj.name == key:
+                    self.text_view.insert(tkinter.INSERT, \
+                        "\n  ")
+                    def make_cb(idx):
+                        def cb():
+                            print("TODO: invntr obj", idx)
+                        return cb
+                    self.text_view.insert(tkinter.INSERT, \
+                        "{} - {}".format(obj.idx, obj.name), \
+                        self.text_hl.add(make_cb(obj.idx)))
+                
+            
 
     def on_open_data(self):
         # open data - select TODO
@@ -510,6 +594,16 @@ class App(tkinter.Frame):
         self.curr_mode = 102
         self.curr_main = 0
         self.update_gui()
+
+    def on_list_names(self):
+        self.curr_mode = 103
+        self.curr_main = 0
+        self.update_gui()
+
+    def on_list_invntr(self):
+        self.curr_mode = 104
+        self.curr_main = 0
+        self.update_gui()
         
     def open_data_from(self, folder):
         self.sim = petka.Engine()
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index b02960844..a6e33cc3e 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -204,11 +204,22 @@ class Engine:
                     raise EngineError("DEBUG: Object ID = 0x{:x} not found".\
                         format(obj[0]))
                         
-        data = self.fman.read_file(self.curr_path + "resource.qrc")
-        mems = io.BytesIO()
-        mems.write(data)
-        mems.seek(0)
-        self.res, self.resord = self.parse_res(mems)
+        f = self.fman.read_file_stream(self.curr_path + "resource.qrc")
+        self.res, self.resord = self.parse_res(f)
+        f.close()
         
+        self.names = {}
+        fp = self.curr_path + "names.ini"
+        if self.fman.exists(fp):
+            f = self.fman.read_file_stream(fp)
+            self.names = self.parse_ini(f)["all"]
+            f.close()
+
+        self.invntr = {}
+        fp = self.curr_path + "invntr.txt"
+        if self.fman.exists(fp):
+            f = self.fman.read_file_stream(fp)
+            self.invntr = self.parse_ini(f)["ALL"]
+            f.close()
         
 
diff --git a/engines/petka/petka/fman.py b/engines/petka/petka/fman.py
index 12435df8a..acb5c362b 100644
--- a/engines/petka/petka/fman.py
+++ b/engines/petka/petka/fman.py
@@ -4,6 +4,7 @@
 
 import os
 import struct
+import io
 
 from . import EngineError
 
@@ -88,6 +89,19 @@ class FileManager:
                 f.close()
             return data
             
+    def read_file_stream(self, fname):
+        data = self.read_file(fname)
+        mems = io.BytesIO()
+        mems.write(data)
+        mems.seek(0)
+        return mems        
+            
+    def exists(self, fname):
+        sf = fname.lower().replace("\\", "/")
+        if sf in self.strtable:
+            return True
+        else:
+            return self.find_path(fname) is not None       
         
     def unload_stores(self, flt = None):
         strfd = []


Commit: 0600e6948cd65dc812ba3987b5ff24639f5e0223
    https://github.com/scummvm/scummvm-tools/commit/0600e6948cd65dc812ba3987b5ff24639f5e0223
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Names anr invntr order as in file

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 0120f61b5..9eb94a535 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -387,12 +387,12 @@ class App(tkinter.Frame):
         elif self.curr_mode == 103:
             # list names
             lb = self.update_gui_add_left_listbox("Names")   
-            for name in self.sim.names.keys():
+            for name in self.sim.namesord:
                 lb.insert(tkinter.END, "{}".format(name))
         elif self.curr_mode == 104:
             # list invntr
             lb = self.update_gui_add_left_listbox("Invntr")   
-            for name in self.sim.invntr.keys():
+            for name in self.sim.invntrord:
                 lb.insert(tkinter.END, "{}".format(name))
         self.update_info()
         self.update_after()
@@ -522,7 +522,7 @@ class App(tkinter.Frame):
         elif self.curr_mode == 103:
             # names
             self.update_info()
-            key = list(self.sim.names.keys())[currsel()]
+            key = self.sim.namesord[currsel()]
             self.text_view.insert(tkinter.INSERT, \
                 "Object: {}\n".format(key))
             self.text_view.insert(tkinter.INSERT, \
@@ -544,7 +544,7 @@ class App(tkinter.Frame):
         elif self.curr_mode == 104:
             # invntr
             self.update_info()
-            key = list(self.sim.invntr.keys())[currsel()]
+            key = self.sim.invntrord[currsel()]
             self.text_view.insert(tkinter.INSERT, \
                 "Object: {}\n".format(key))
             self.text_view.insert(tkinter.INSERT, \
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index a6e33cc3e..64a5bb77c 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -33,20 +33,27 @@ class Engine:
         # parse ini settings
         curr_sect = None
         ini = {}
-        ordersect = []
+        order_sect = []
+        orders = {}
         for line in f.readlines():
             line = line.decode(self.enc).strip()
             if len(line) == 0: continue
             if line[:1] == ";": continue
             if line[:1] == "[" and line[-1:] == "]":
+                if curr_sect is not None:
+                    orders[curr_sect] = order
                 curr_sect = line[1:-1].strip()
+                order_sect.append(curr_sect)
+                order = []
                 ini[curr_sect] = {}
-                ordersect.append(curr_sect)
                 continue
             kv = line.split("=", 1)
             if len(kv) != 2: continue
             ini[curr_sect][kv[0].strip()] = kv[1].strip()
-        ini["__order__"] = ordersect
+            order.append(kv[0].strip())
+        orders[curr_sect] = order
+        ini["__ordersect__"] = order_sect
+        ini["__order__"] = orders
         return ini
         
     def parse_res(self, f):
@@ -78,7 +85,7 @@ class Engine:
                 self.parts_ini = self.parse_ini(f)
             finally:
                 f.close()
-            for sect in self.parts_ini["__order__"]:
+            for sect in self.parts_ini["__ordersect__"]:
                 data = self.parts_ini[sect]
                 if sect == "All":
                     if "Part" in data:
@@ -212,14 +219,18 @@ class Engine:
         fp = self.curr_path + "names.ini"
         if self.fman.exists(fp):
             f = self.fman.read_file_stream(fp)
-            self.names = self.parse_ini(f)["all"]
+            ini = self.parse_ini(f)
+            self.names = ini["all"]
+            self.namesord = ini["__order__"]["all"]
             f.close()
 
         self.invntr = {}
         fp = self.curr_path + "invntr.txt"
         if self.fman.exists(fp):
             f = self.fman.read_file_stream(fp)
-            self.invntr = self.parse_ini(f)["ALL"]
+            ini = self.parse_ini(f)
+            self.invntr = ini["ALL"]
+            self.invntrord = ini["__order__"]["ALL"]
             f.close()
         
 


Commit: 82c8104b1435726260c72f9ca7ec741aab4227fa
    https://github.com/scummvm/scummvm-tools/commit/82c8104b1435726260c72f9ca7ec741aab4227fa
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Open aliases and invntr from objects, refactor

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 9eb94a535..2ffd1a3ff 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -6,6 +6,7 @@
 import sys, os
 import tkinter
 from tkinter import ttk, font
+from idlelib.WidgetRedirector import WidgetRedirector
 
 import petka
 
@@ -42,6 +43,17 @@ class HyperlinkManager:
                 self.links[tag]()
                 return
 		
+		
+# thanx http://tkinter.unpythonic.net/wiki/ReadOnlyText
+class ReadOnlyText(tkinter.Text):
+    def __init__(self, *args, **kwargs):
+        tkinter.Text.__init__(self, *args, **kwargs)
+        self.redirector = WidgetRedirector(self)
+        self.insert = \
+            self.redirector.register("insert", lambda *args, **kw: "break")
+        self.delete = \
+            self.redirector.register("delete", lambda *args, **kw: "break")
+		
 class App(tkinter.Frame):
     def __init__(self, master):
         tkinter.Frame.__init__(self, master)
@@ -50,9 +62,9 @@ class App(tkinter.Frame):
         self.pad = None
         self.sim = None
         # gui
+        self.curr_main = 0 # 0 - frame, 1 - canvas
         self.curr_mode = 0
         self.curr_gui = []
-        self.curr_main = 0 # 0 - frame, 1 - canvas
         self.curr_lb_acts = None
         # canvas
         self.curr_width = 0
@@ -98,13 +110,12 @@ class App(tkinter.Frame):
         self.canv_view.bind('<ButtonPress-1>', self.on_mouse_view)
         
         # text
-        self.text_view = tkinter.Text(self.frm_view,
+        self.text_view = ReadOnlyText(self.frm_view,
             highlightthickness = 0,
             )
         self.text_hl = HyperlinkManager(self.text_view)
         self.text_view.bind('<Configure>', self.on_resize_view)
         
-        
         self.update_after()
         self.update_gui()
 
@@ -337,9 +348,7 @@ class App(tkinter.Frame):
                     self.curr_height = self.main_image.height()
                     self.update_gui()
                 def tst_info():
-                    self.curr_mode = 99
-                    self.curr_main = 0
-                    self.update_gui()
+                    self.change_gui(0, 99)
                 acts = [
                     ("Parts", self.on_list_parts),
                     ("Resources ({})".format(len(self.sim.res)), \
@@ -448,6 +457,42 @@ class App(tkinter.Frame):
             stdinfo()
         else:
             stdinfo()
+            
+    def open_gui_elem(self, main, mode, idx):
+        if self.curr_mode != mode:
+            self.change_gui(main, mode)
+        self.curr_lb.selection_set(idx)
+        self.curr_lb.see(idx)
+        self.on_left_listbox(None)
+            
+    def open_object(self, obj_id):
+        for idx, obj in enumerate(self.sim.objects):
+            if obj.idx == obj_id:
+                self.open_gui_elem(0, 101, idx)
+                break
+
+    def open_scene(self, scn_id):
+        for idx, obj in enumerate(self.sim.scenes):
+            if obj.idx == scn_id:
+                self.open_gui_elem(0, 102, idx)
+                break
+
+    def open_name(self, name_key):
+        for idx, key in enumerate(self.sim.namesord):
+            if key == name_key:
+                self.open_gui_elem(0, 103, idx)
+                break
+
+    def open_invntr(self, inv_key):
+        for idx, key in enumerate(self.sim.invntrord):
+            if key == inv_key:
+                self.open_gui_elem(0, 104, idx)
+                break
+                
+    def change_gui(self, main, mode):
+        self.curr_main = main
+        self.curr_mode = mode
+        self.update_gui()
 
     def on_left_listbox(self, event):
         def currsel():
@@ -464,12 +509,25 @@ class App(tkinter.Frame):
             self.text_view.insert(tkinter.INSERT, \
                 "  Index:  {}\n  Name:   {}\n".format(rec.idx, rec.name))
             if rec.name in self.sim.names:
+                self.text_view.insert(tkinter.INSERT, "  ")
+                def make_cb(key):
+                    def cb():
+                        self.open_name(key)
+                    return cb
+                self.text_view.insert(tkinter.INSERT, "Alias", \
+                    self.text_hl.add(make_cb(rec.name)))
                 self.text_view.insert(tkinter.INSERT, \
-                    "  Alias:  {}\n".format(self.sim.names[rec.name]))
+                    ":  {}\n".format(self.sim.names[rec.name]))
             if rec.name in self.sim.invntr:
+                self.text_view.insert(tkinter.INSERT, "  ")
+                def make_cb(key):
+                    def cb():
+                        self.open_invntr(key)
+                    return cb
+                self.text_view.insert(tkinter.INSERT, "Invntr", \
+                    self.text_hl.add(make_cb(rec.name)))
                 self.text_view.insert(tkinter.INSERT, \
-                    "  Invntr: {}\n".format(self.sim.invntr[rec.name]))
-                
+                    ": {}\n".format(self.sim.invntr[rec.name]))
         if self.curr_lb_acts:
             act = self.curr_lb_acts[currsel()]
             if act[1]:
@@ -524,9 +582,9 @@ class App(tkinter.Frame):
             self.update_info()
             key = self.sim.namesord[currsel()]
             self.text_view.insert(tkinter.INSERT, \
-                "Object: {}\n".format(key))
+                "Alias: {}\n".format(key))
             self.text_view.insert(tkinter.INSERT, \
-                "Alias:  {}\n\n".format(self.sim.names[key]))
+                "Value: {}\n\n".format(self.sim.names[key]))
             # search for objects
             self.text_view.insert(tkinter.INSERT, \
                 "Applied for:")
@@ -536,7 +594,7 @@ class App(tkinter.Frame):
                         "\n  ")
                     def make_cb(idx):
                         def cb():
-                            print("TODO: names obj", idx)
+                            self.open_object(idx)
                         return cb
                     self.text_view.insert(tkinter.INSERT, \
                         "{} - {}".format(obj.idx, obj.name), \
@@ -546,7 +604,7 @@ class App(tkinter.Frame):
             self.update_info()
             key = self.sim.invntrord[currsel()]
             self.text_view.insert(tkinter.INSERT, \
-                "Object: {}\n".format(key))
+                "Invntr: {}\n".format(key))
             self.text_view.insert(tkinter.INSERT, \
                 "{}\n\n".format(self.sim.invntr[key]))
             # search for objects
@@ -558,52 +616,36 @@ class App(tkinter.Frame):
                         "\n  ")
                     def make_cb(idx):
                         def cb():
-                            print("TODO: invntr obj", idx)
+                            self.open_object(idx)
                         return cb
                     self.text_view.insert(tkinter.INSERT, \
                         "{} - {}".format(obj.idx, obj.name), \
                         self.text_hl.add(make_cb(obj.idx)))
-                
-            
 
     def on_open_data(self):
         # open data - select TODO
         pass
         
     def on_outline(self):
-        self.curr_mode = 0
-        self.curr_main = 0
-        self.update_gui()
+        self.change_gui(0, 0)
         
     def on_list_parts(self):
-        self.curr_mode = 90
-        self.curr_main = 0
-        self.update_gui()
+            self.change_gui(0, 90)
 
     def on_list_res(self):
-        self.curr_mode = 100
-        self.curr_main = 1
-        self.update_gui()
+        self.change_gui(1, 100)
 
     def on_list_objs(self):
-        self.curr_mode = 101
-        self.curr_main = 0
-        self.update_gui()
+        self.change_gui(0, 101)
 
     def on_list_scenes(self):
-        self.curr_mode = 102
-        self.curr_main = 0
-        self.update_gui()
+        self.change_gui(0, 102)
 
     def on_list_names(self):
-        self.curr_mode = 103
-        self.curr_main = 0
-        self.update_gui()
+        self.change_gui(0, 103)
 
     def on_list_invntr(self):
-        self.curr_mode = 104
-        self.curr_main = 0
-        self.update_gui()
+        self.change_gui(0, 104)
         
     def open_data_from(self, folder):
         self.sim = petka.Engine()


Commit: b62ea77dc2cfe68d532e65fde66d3b22e98f1092
    https://github.com/scummvm/scummvm-tools/commit/b62ea77dc2cfe68d532e65fde66d3b22e98f1092
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Cleanup removed links

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 2ffd1a3ff..a4c20d92f 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -21,6 +21,7 @@ class HyperlinkManager:
         self.text.tag_bind("hyper", "<Leave>", self._leave)
         self.text.tag_bind("hyper", "<Button-1>", self._click)
         self.reset()
+
     def reset(self):
     	self.links = {}
 
@@ -374,33 +375,33 @@ class App(tkinter.Frame):
             self.update_gui_add_left_listbox("Test info", acts)                
         elif self.curr_mode == 90:
             # list parts
-            lb = self.update_gui_add_left_listbox("Parts")   
+            lb = self.update_gui_add_left_listbox("Parts")
             for part in self.sim.parts:
                 lb.insert(tkinter.END, part)
         elif self.curr_mode == 100:
             # list resources
-            lb = self.update_gui_add_left_listbox("Resources")   
+            lb = self.update_gui_add_left_listbox("Resources") 
             for res_id in self.sim.resord:
                 lb.insert(tkinter.END, "{} - {}".format(res_id, \
                     self.sim.res[res_id]))
         elif self.curr_mode == 101:
             # list objects
-            lb = self.update_gui_add_left_listbox("Objects")   
+            lb = self.update_gui_add_left_listbox("Objects")
             for obj in self.sim.objects:
                 lb.insert(tkinter.END, "{} - {}".format(obj.idx, obj.name))
         elif self.curr_mode == 102:
             # list scenes
-            lb = self.update_gui_add_left_listbox("Scenes")   
+            lb = self.update_gui_add_left_listbox("Scenes")
             for scn in self.sim.scenes:
                 lb.insert(tkinter.END, "{} - {}".format(scn.idx, scn.name))
         elif self.curr_mode == 103:
             # list names
-            lb = self.update_gui_add_left_listbox("Names")   
+            lb = self.update_gui_add_left_listbox("Names")
             for name in self.sim.namesord:
                 lb.insert(tkinter.END, "{}".format(name))
         elif self.curr_mode == 104:
             # list invntr
-            lb = self.update_gui_add_left_listbox("Invntr")   
+            lb = self.update_gui_add_left_listbox("Invntr")
             for name in self.sim.invntrord:
                 lb.insert(tkinter.END, "{}".format(name))
         self.update_info()
@@ -412,7 +413,8 @@ class App(tkinter.Frame):
             self.text_view.insert(tkinter.INSERT, "<- Outline", \
                 self.text_hl.add(self.on_outline))
             self.text_view.insert(tkinter.INSERT, "\n\n")
-           
+
+        self.text_hl.reset()
         if self.curr_mode == 0:
             self.text_view.delete(0.0, tkinter.END)
             if self.sim is None:


Commit: 4d17a6243e53f9fd6facb90803d3c0dcab6c06d7
    https://github.com/scummvm/scummvm-tools/commit/4d17a6243e53f9fd6facb90803d3c0dcab6c06d7
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Cleanup removed links

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index a4c20d92f..3140ec69e 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -632,10 +632,10 @@ class App(tkinter.Frame):
         self.change_gui(0, 0)
         
     def on_list_parts(self):
-            self.change_gui(0, 90)
+        self.change_gui(0, 90)
 
     def on_list_res(self):
-        self.change_gui(1, 100)
+        self.change_gui(0, 100)
 
     def on_list_objs(self):
         self.change_gui(0, 101)


Commit: 715ed6ef51631ed145a2d7a92ce901030aa1b7cc
    https://github.com/scummvm/scummvm-tools/commit/715ed6ef51631ed145a2d7a92ce901030aa1b7cc
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Resource filters

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 3140ec69e..9de1f23b4 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -65,6 +65,7 @@ class App(tkinter.Frame):
         # gui
         self.curr_main = 0 # 0 - frame, 1 - canvas
         self.curr_mode = 0
+        self.curr_mode_sub = None
         self.curr_gui = []
         self.curr_lb_acts = None
         # canvas
@@ -380,10 +381,19 @@ class App(tkinter.Frame):
                 lb.insert(tkinter.END, part)
         elif self.curr_mode == 100:
             # list resources
-            lb = self.update_gui_add_left_listbox("Resources") 
-            for res_id in self.sim.resord:
-                lb.insert(tkinter.END, "{} - {}".format(res_id, \
-                    self.sim.res[res_id]))
+            if self.curr_mode_sub is None:
+                lb = self.update_gui_add_left_listbox("Resources") 
+                for res_id in self.sim.resord:
+                    lb.insert(tkinter.END, "{} - {}".format(res_id, \
+                        self.sim.res[res_id]))
+            else:
+                lb = self.update_gui_add_left_listbox("Resources: {}".\
+                    format(self.curr_mode_sub))
+                for res_id in self.sim.resord:
+                    if self.sim.res[res_id].upper().endswith\
+                        ("." + self.curr_mode_sub):
+                        lb.insert(tkinter.END, "{} - {}".format(res_id, \
+                            self.sim.res[res_id]))
         elif self.curr_mode == 101:
             # list objects
             lb = self.update_gui_add_left_listbox("Objects")
@@ -453,6 +463,31 @@ class App(tkinter.Frame):
             self.text_view.insert(tkinter.INSERT, "{}".\
                 format(len(self.sim.invntr)), \
                 self.text_hl.add(self.on_list_invntr))
+        elif self.curr_mode == 100:
+            stdinfo()
+            self.text_view.insert(tkinter.INSERT, "Total: ")
+            self.text_view.insert(tkinter.INSERT, "{}".\
+                format(len(self.sim.res)), \
+                self.text_hl.add(lambda: self.change_gui(0, 100)))
+            self.text_view.insert(tkinter.INSERT, "\nFiletypes:\n")
+            fts = {}
+            for res in self.sim.res.values():
+                fp = res.rfind(".")
+                if fp >= 0:
+                    ft = res[fp + 1:].upper()
+                    fts[ft] = fts.get(ft, 0) + 1
+            ftk = list(fts.keys())
+            ftk.sort()
+            for ft in ftk:
+                self.text_view.insert(tkinter.INSERT, "  ")
+                def make_cb(key):
+                    def cb():
+                        self.change_gui(0, 100, key)
+                    return cb
+                self.text_view.insert(tkinter.INSERT, ft, \
+                    self.text_hl.add(make_cb(ft)))
+                self.text_view.insert(tkinter.INSERT, ": {}\n".format(fts[ft]))
+                
         elif self.curr_mode == 101:
             stdinfo()
         elif self.curr_mode == 102:
@@ -491,9 +526,10 @@ class App(tkinter.Frame):
                 self.open_gui_elem(0, 104, idx)
                 break
                 
-    def change_gui(self, main, mode):
+    def change_gui(self, main, mode, sub = None):
         self.curr_main = main
         self.curr_mode = mode
+        self.curr_mode_sub = sub
         self.update_gui()
 
     def on_left_listbox(self, event):
@@ -557,19 +593,21 @@ class App(tkinter.Frame):
             # resources
             try:
                 res_id = self.curr_lb.curselection()[0]
+                res_id = self.curr_lb.get(res_id).split("-", 1)[0].strip()
                 res_id = int(res_id)
             except:
                 pass
-            res_id = self.sim.resord[res_id]
             fn = self.sim.res[res_id]
             if fn[-4:].lower() == ".bmp":
                 bmpdata = self.sim.fman.read_file(fn)
                 bmp = petka.BMPLoader()
                 bmp.load_data(bmpdata)
-                self.main_image = self.make_image(bmp.width, bmp.height, bmp.rgb)
+                self.main_image = \
+                    self.make_image(bmp.width, bmp.height, bmp.rgb)
                 self.curr_width = bmp.width
                 self.curr_height = bmp.height
-                self.update_after()
+                self.curr_main = 1
+                self.update_gui()
             print(fn)
         elif self.curr_mode == 101:
             # objects


Commit: 47a787d10e37435e878e8d447ccc164bfe8f651f
    https://github.com/scummvm/scummvm-tools/commit/47a787d10e37435e878e8d447ccc164bfe8f651f
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Refactor, scene referenced objects

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 9de1f23b4..6e5df0a41 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -307,6 +307,11 @@ class App(tkinter.Frame):
         self.curr_lb = lb
         return lb
     
+    def make_obj_cb(self, idx):
+        def cb():
+            self.open_object(idx)
+        return cb
+    
     def update_gui(self):
         self.canv_view.delete(tkinter.ALL)
         for item in self.curr_gui:
@@ -352,7 +357,8 @@ class App(tkinter.Frame):
                 def tst_info():
                     self.change_gui(0, 99)
                 acts = [
-                    ("Parts", self.on_list_parts),
+                    ("Parts ({})".format(len(self.sim.parts)), \
+                        self.on_list_parts),
                     ("Resources ({})".format(len(self.sim.res)), \
                         self.on_list_res),
                     ("Objects ({})".format(len(self.sim.objects)), \
@@ -417,59 +423,53 @@ class App(tkinter.Frame):
         self.update_info()
         self.update_after()
 
+    def insert_text(self, text, link = None):
+        if link:
+            self.text_view.insert(tkinter.INSERT, text, self.text_hl.add(link))
+        else:
+            self.text_view.insert(tkinter.INSERT, text)
+
     def update_info(self):
         def stdinfo():
             self.text_view.delete(0.0, tkinter.END)
-            self.text_view.insert(tkinter.INSERT, "<- Outline", \
-                self.text_hl.add(self.on_outline))
-            self.text_view.insert(tkinter.INSERT, "\n\n")
+            self.insert_text("<- Outline", self.on_outline)
+            self.insert_text("\n\n")
 
         self.text_hl.reset()
         if self.curr_mode == 0:
             self.text_view.delete(0.0, tkinter.END)
             if self.sim is None:
-                self.text_view.insert(tkinter.INSERT, "No data loaded")
-                self.text_view.insert(tkinter.INSERT, "Open data", \
-                    self.text_hl.add(self.on_open_data))
+                self.insert_text("No data loaded")
+                self.insert_text("Open data",self.on_open_data)
             else:
-                self.text_view.insert(tkinter.INSERT, \
-                    "Select type from outline")
+                self.insert_text("Select type from outline")
         elif self.curr_mode == 99:
             stdinfo()
             for i in range(100):
-                self.text_view.insert(tkinter.INSERT, \
-                    "Item {}\n".format(i))
+                self.insert_text("Item {}\n".format(i))
         elif self.curr_mode == 90:
             stdinfo()
-            self.text_view.insert(tkinter.INSERT, \
-                "Current: part {} chapter {}\n\n  Resources: ".\
+            self.insert_text("Current: part {} chapter {}\n\n  Resources: ".\
                     format(self.sim.curr_part, self.sim.curr_chap))
-            self.text_view.insert(tkinter.INSERT, "{}".\
-                format(len(self.sim.res)), \
-                self.text_hl.add(self.on_list_res))
-            self.text_view.insert(tkinter.INSERT, "\n  Objects:   ")
-            self.text_view.insert(tkinter.INSERT, "{}".\
-                format(len(self.sim.objects)), \
-                self.text_hl.add(self.on_list_objs))
-            self.text_view.insert(tkinter.INSERT, "\n  Scenes:    ")
-            self.text_view.insert(tkinter.INSERT, "{}".\
-                format(len(self.sim.scenes)), \
-                self.text_hl.add(self.on_list_scenes))
-            self.text_view.insert(tkinter.INSERT, "\n  Names:     ")
-            self.text_view.insert(tkinter.INSERT, "{}".\
-                format(len(self.sim.names)), \
-                self.text_hl.add(self.on_list_names))
-            self.text_view.insert(tkinter.INSERT, "\n  Invntr:    ")
-            self.text_view.insert(tkinter.INSERT, "{}".\
-                format(len(self.sim.invntr)), \
-                self.text_hl.add(self.on_list_invntr))
+            self.insert_text("{}".format(len(self.sim.res)), self.on_list_res)
+            self.insert_text("\n  Objects:   ")
+            self.insert_text("{}".format(len(self.sim.objects)), \
+                self.on_list_objs)
+            self.insert_text("\n  Scenes:    ")
+            self.insert_text("{}".format(len(self.sim.scenes)), \
+                self.on_list_scenes)
+            self.insert_text("\n  Names:     ")
+            self.insert_text("{}".format(len(self.sim.names)), \
+                self.on_list_names)
+            self.insert_text("\n  Invntr:    ")
+            self.insert_text("{}".format(len(self.sim.invntr)), \
+                self.on_list_invntr)
         elif self.curr_mode == 100:
             stdinfo()
-            self.text_view.insert(tkinter.INSERT, "Total: ")
-            self.text_view.insert(tkinter.INSERT, "{}".\
-                format(len(self.sim.res)), \
-                self.text_hl.add(lambda: self.change_gui(0, 100)))
-            self.text_view.insert(tkinter.INSERT, "\nFiletypes:\n")
+            self.insert_text("Total: ")
+            self.insert_text("{}".format(len(self.sim.res)), \
+                lambda: self.change_gui(0, 100))
+            self.insert_text("\nFiletypes:\n")
             fts = {}
             for res in self.sim.res.values():
                 fp = res.rfind(".")
@@ -479,14 +479,13 @@ class App(tkinter.Frame):
             ftk = list(fts.keys())
             ftk.sort()
             for ft in ftk:
-                self.text_view.insert(tkinter.INSERT, "  ")
+                self.insert_text("  ")
                 def make_cb(key):
                     def cb():
                         self.change_gui(0, 100, key)
                     return cb
-                self.text_view.insert(tkinter.INSERT, ft, \
-                    self.text_hl.add(make_cb(ft)))
-                self.text_view.insert(tkinter.INSERT, ": {}\n".format(fts[ft]))
+                self.insert_text(ft, make_cb(ft))
+                self.insert_text(": {}\n".format(fts[ft]))
                 
         elif self.curr_mode == 101:
             stdinfo()
@@ -542,30 +541,37 @@ class App(tkinter.Frame):
             return num
 
         def objinfo(tp, rec):
-            self.text_view.insert(tkinter.INSERT, \
-                ("Object" if tp else "Scene") + ":\n")
-            self.text_view.insert(tkinter.INSERT, \
-                "  Index:  {}\n  Name:   {}\n".format(rec.idx, rec.name))
+            self.insert_text(("Object" if tp else "Scene") + ":\n")
+            self.insert_text("  Index: {}\n  Name:  {}\n".\
+                format(rec.idx, rec.name))
             if rec.name in self.sim.names:
-                self.text_view.insert(tkinter.INSERT, "  ")
+                self.insert_text("  ")
                 def make_cb(key):
                     def cb():
                         self.open_name(key)
                     return cb
-                self.text_view.insert(tkinter.INSERT, "Alias", \
-                    self.text_hl.add(make_cb(rec.name)))
-                self.text_view.insert(tkinter.INSERT, \
-                    ":  {}\n".format(self.sim.names[rec.name]))
+                self.insert_text("Alias", make_cb(rec.name))
+                self.insert_text(":  {}\n".format(self.sim.names[rec.name]))
             if rec.name in self.sim.invntr:
-                self.text_view.insert(tkinter.INSERT, "  ")
+                self.insert_text("  ")
                 def make_cb(key):
                     def cb():
                         self.open_invntr(key)
                     return cb
-                self.text_view.insert(tkinter.INSERT, "Invntr", \
-                    self.text_hl.add(make_cb(rec.name)))
-                self.text_view.insert(tkinter.INSERT, \
-                    ": {}\n".format(self.sim.invntr[rec.name]))
+                self.insert_text("Invntr", make_cb(rec.name))
+                self.insert_text(": {}\n".format(self.sim.invntr[rec.name]))
+                    
+            if not tp:
+                if len(rec.refs) == 0:
+                    self.insert_text("\nNo references\n")
+                else:
+                    self.insert_text("\nReferences: {}\n".format(len(rec.refs)))
+                for idx, ref in enumerate(rec.refs):
+                    self.insert_text("  {}) ".format(idx))
+                    self.insert_text("obj_{}".format(ref[0].idx), \
+                        self.make_obj_cb(ref[0].idx))
+                    self.insert_text("\n".format(idx))
+                    
         if self.curr_lb_acts:
             act = self.curr_lb_acts[currsel()]
             if act[1]:
@@ -621,46 +627,36 @@ class App(tkinter.Frame):
             # names
             self.update_info()
             key = self.sim.namesord[currsel()]
-            self.text_view.insert(tkinter.INSERT, \
-                "Alias: {}\n".format(key))
-            self.text_view.insert(tkinter.INSERT, \
-                "Value: {}\n\n".format(self.sim.names[key]))
+            self.insert_text("Alias: {}\n".format(key))
+            self.insert_text("Value: {}\n\n".format(self.sim.names[key]))
             # search for objects
-            self.text_view.insert(tkinter.INSERT, \
-                "Applied for:")
+            self.insert_text("Applied for:")
             for obj in self.sim.objects:
                 if obj.name == key:
-                    self.text_view.insert(tkinter.INSERT, \
-                        "\n  ")
+                    self.insert_text("\n  ")
                     def make_cb(idx):
                         def cb():
                             self.open_object(idx)
                         return cb
-                    self.text_view.insert(tkinter.INSERT, \
-                        "{} - {}".format(obj.idx, obj.name), \
-                        self.text_hl.add(make_cb(obj.idx)))
+                    self.insert_text("{} - {}".format(obj.idx, obj.name), \
+                        make_cb(obj.idx))
         elif self.curr_mode == 104:
             # invntr
             self.update_info()
             key = self.sim.invntrord[currsel()]
-            self.text_view.insert(tkinter.INSERT, \
-                "Invntr: {}\n".format(key))
-            self.text_view.insert(tkinter.INSERT, \
-                "{}\n\n".format(self.sim.invntr[key]))
+            self.insert_text("Invntr: {}\n".format(key))
+            self.insert_text("{}\n\n".format(self.sim.invntr[key]))
             # search for objects
-            self.text_view.insert(tkinter.INSERT, \
-                "Applied for:")
+            self.insert_text("Applied for:")
             for obj in self.sim.objects:
                 if obj.name == key:
-                    self.text_view.insert(tkinter.INSERT, \
-                        "\n  ")
+                    self.insert_text("\n  ")
                     def make_cb(idx):
                         def cb():
                             self.open_object(idx)
                         return cb
-                    self.text_view.insert(tkinter.INSERT, \
-                        "{} - {}".format(obj.idx, obj.name), \
-                        self.text_hl.add(make_cb(obj.idx)))
+                    self.insert_text("{} - {}".format(obj.idx, obj.name), \
+                        make_cb(obj.idx))
 
     def on_open_data(self):
         # open data - select TODO
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 64a5bb77c..9c4a34335 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -216,6 +216,7 @@ class Engine:
         f.close()
         
         self.names = {}
+        self.namesord = []
         fp = self.curr_path + "names.ini"
         if self.fman.exists(fp):
             f = self.fman.read_file_stream(fp)
@@ -225,6 +226,7 @@ class Engine:
             f.close()
 
         self.invntr = {}
+        self.invntrord = []
         fp = self.curr_path + "invntr.txt"
         if self.fman.exists(fp):
             f = self.fman.read_file_stream(fp)


Commit: 26b39b9aa3d62c348527bf34d5fcc0c54236bab2
    https://github.com/scummvm/scummvm-tools/commit/26b39b9aa3d62c348527bf34d5fcc0c54236bab2
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Refactor navigation

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 6e5df0a41..000fb611d 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -63,7 +63,10 @@ class App(tkinter.Frame):
         self.pad = None
         self.sim = None
         # gui
+        self.path_handler = {}
         self.curr_main = 0 # 0 - frame, 1 - canvas
+        self.curr_path = []
+        self.last_path = None
         self.curr_mode = 0
         self.curr_mode_sub = None
         self.curr_gui = []
@@ -118,8 +121,11 @@ class App(tkinter.Frame):
         self.text_hl = HyperlinkManager(self.text_view)
         self.text_view.bind('<Configure>', self.on_resize_view)
         
+        # bind path handlers
+        self.path_handler["parts"] = self.path_parts
+        
         self.update_after()
-        self.update_gui()
+        self.open_path([])
 
     def create_menu(self):
         self.menubar = tkinter.Menu(self.master)
@@ -140,27 +146,27 @@ class App(tkinter.Frame):
         self.menubar.add_cascade(menu = self.menuedit,
                 label = "Edit")
         self.menuedit.add_command(
-                command = self.on_outline,
+                command = lambda: self.open_path([]),
                 label = "Outline")
         self.menuedit.add_separator()
         self.menuedit.add_command(
-                command = self.on_list_parts,
+                command = lambda: self.open_path(["parts"]),
                 label = "Select part")
         self.menuedit.add_separator()
         self.menuedit.add_command(
-                command = self.on_list_res,
+                command = lambda: self.open_path(["res"]),
                 label = "Resources")
         self.menuedit.add_command(
-                command = self.on_list_objs,
+                command = lambda: self.open_path(["objs"]),
                 label = "Objects")
         self.menuedit.add_command(
-                command = self.on_list_scenes,
+                command = lambda: self.open_path(["scenes"]),
                 label = "Scenes")
         self.menuedit.add_command(
-                command = self.on_list_names,
+                command = lambda: self.open_path(["names"]),
                 label = "Names")
         self.menuedit.add_command(
-                command = self.on_list_invntr,
+                command = lambda: self.open_path(["invntr"]),
                 label = "Invntr")
 
     def update_after(self):
@@ -185,13 +191,19 @@ class App(tkinter.Frame):
         self.master.destroy()
 
     def on_mouse_view(self, event):
-        #self.currMode += 1
-        #if self.currMode > 1:
-        #    self.currMode = 0
         self.update_after()
         
     def on_resize_view(self, event):
         self.update_after()
+ 
+    def open_path(self, path):
+        path = tuple(path)
+        print("DEBUG: Open", path)
+        self.curr_path = path
+        if len(path) > 0:
+            if path[0] in self.path_handler:
+                return self.path_handler[path[0]](path)
+        return self.path_default(path)
 
     def update_canvas(self):
         if self.curr_main == 0:          
@@ -275,10 +287,21 @@ class App(tkinter.Frame):
             data = bytes(p))
         return image                
 
-    def update_gui_add_left_listbox(self, text, acts = None):
+    def make_obj_cb(self, idx):
+        def cb():
+            self.open_object(idx)
+        return cb
+    
+    def update_gui(self, text = "<Undefined>"):
+        self.last_path = self.curr_path
+        self.canv_view.delete(tkinter.ALL)
+        # cleanup
+        for item in self.curr_gui:
+            item()
+        self.curr_gui = []
+        # left listbox
         lab = tkinter.Label(self.frm_left, text = text)
         lab.pack()
-        
         frm_lb = ttk.Frame(self.frm_left)
         frm_lb.pack(fill = tkinter.BOTH, expand = 1)
         frm_lb.grid_rowconfigure(0, weight = 1)
@@ -299,25 +322,10 @@ class App(tkinter.Frame):
         self.curr_gui.append(lambda:frm_lb.pack_forget())
         lb.bind("<Double-Button-1>", self.on_left_listbox)
         lb.bind("<Return>", self.on_left_listbox)
-
-        self.curr_lb_acts = acts
-        if acts:
-            for name, cb in acts:
-                lb.insert(tkinter.END, name)
+        # actions on listbox
         self.curr_lb = lb
-        return lb
-    
-    def make_obj_cb(self, idx):
-        def cb():
-            self.open_object(idx)
-        return cb
-    
-    def update_gui(self):
-        self.canv_view.delete(tkinter.ALL)
-        for item in self.curr_gui:
-            item()
-        self.curr_gui = []
-
+        self.curr_lb_acts = []
+        # main view
         if self.curr_main == 0:
             self.canv_view.grid_forget()
             self.text_view.grid(row = 0, column = 0, \
@@ -338,43 +346,11 @@ class App(tkinter.Frame):
             )
             self.scr_view_x.config(command = self.canv_view.xview)
             self.scr_view_y.config(command = self.canv_view.yview)
-
+        return
+        
+        
         if self.curr_mode == 0:
-            if self.sim is None:
-                # open some data
-                acts = [
-                    ("Open data", self.on_open_data)
-                ]
-                self.update_gui_add_left_listbox("Outline", acts)                
-            else:
-                def tst_img():
-                    self.curr_main = 1
-                    self.main_image = tkinter.PhotoImage(\
-                        file = "img/splash.gif")
-                    self.curr_width = self.main_image.width()
-                    self.curr_height = self.main_image.height()
-                    self.update_gui()
-                def tst_info():
-                    self.change_gui(0, 99)
-                acts = [
-                    ("Parts ({})".format(len(self.sim.parts)), \
-                        self.on_list_parts),
-                    ("Resources ({})".format(len(self.sim.res)), \
-                        self.on_list_res),
-                    ("Objects ({})".format(len(self.sim.objects)), \
-                        self.on_list_objs),
-                    ("Scenes ({})".format(len(self.sim.scenes)), \
-                        self.on_list_scenes),
-                    ("Names ({})".format(len(self.sim.names)), \
-                        self.on_list_names),
-                    ("Invntr ({})".format(len(self.sim.invntr)), \
-                        self.on_list_invntr),
-                    ("-", None),
-                    ("Test image", tst_img),
-                    ("Test info", tst_info),
-                ]
-                self.update_gui_add_left_listbox("Outline: part {} chapter {}".\
-                    format(self.sim.curr_part, self.sim.curr_chap), acts)                
+            pass
         elif self.curr_mode == 99:
             acts = [
                 ("<- outline", self.on_outline)
@@ -423,12 +399,27 @@ class App(tkinter.Frame):
         self.update_info()
         self.update_after()
 
+    def clear_text(self):
+        self.text_view.delete(0.0, tkinter.END)
+
     def insert_text(self, text, link = None):
         if link:
-            self.text_view.insert(tkinter.INSERT, text, self.text_hl.add(link))
+            if callable(link):
+                cb = link
+            else: 
+                def make_cb(path):
+                    def cb():
+                        return self.open_path(path)
+                    return cb
+                cb = make_cb(tuple(link))
+            self.text_view.insert(tkinter.INSERT, text, self.text_hl.add(cb))
         else:
             self.text_view.insert(tkinter.INSERT, text)
 
+    def insert_lb_act(self, name, act):
+        self.curr_lb_acts.append((name, act))
+        self.curr_lb.insert(tkinter.END, name)
+
     def update_info(self):
         def stdinfo():
             self.text_view.delete(0.0, tkinter.END)
@@ -449,21 +440,6 @@ class App(tkinter.Frame):
                 self.insert_text("Item {}\n".format(i))
         elif self.curr_mode == 90:
             stdinfo()
-            self.insert_text("Current: part {} chapter {}\n\n  Resources: ".\
-                    format(self.sim.curr_part, self.sim.curr_chap))
-            self.insert_text("{}".format(len(self.sim.res)), self.on_list_res)
-            self.insert_text("\n  Objects:   ")
-            self.insert_text("{}".format(len(self.sim.objects)), \
-                self.on_list_objs)
-            self.insert_text("\n  Scenes:    ")
-            self.insert_text("{}".format(len(self.sim.scenes)), \
-                self.on_list_scenes)
-            self.insert_text("\n  Names:     ")
-            self.insert_text("{}".format(len(self.sim.names)), \
-                self.on_list_names)
-            self.insert_text("\n  Invntr:    ")
-            self.insert_text("{}".format(len(self.sim.invntr)), \
-                self.on_list_invntr)
         elif self.curr_mode == 100:
             stdinfo()
             self.insert_text("Total: ")
@@ -575,26 +551,11 @@ class App(tkinter.Frame):
         if self.curr_lb_acts:
             act = self.curr_lb_acts[currsel()]
             if act[1]:
-                act[1]()
-        elif self.curr_mode == 90:
-            # parts
-            try:
-                part_id = self.curr_lb.curselection()[0]
-                part_id = int(part_id)
-            except:
-                pass
-            part_id = self.sim.parts[part_id]
-            # parse
-            pnum = part_id[5:]
-            cnum = pnum.split("Chapter", 1)
-            if len(cnum) > 1:
-                pnum = int(cnum[0].strip(), 10)
-                cnum = int(cnum[1].strip(), 10)
-            else:
-                cnum = 0
-            self.sim.open_part(pnum, cnum)
-            self.update_info()
-            self.update_after()
+                self.open_path(act[1])
+        return
+        
+        if self.curr_mode == 90:
+            pass
         elif self.curr_mode == 100:
             # resources
             try:
@@ -658,31 +619,83 @@ class App(tkinter.Frame):
                     self.insert_text("{} - {}".format(obj.idx, obj.name), \
                         make_cb(obj.idx))
 
+    def path_default(self, path):
+        self.curr_main = 0
+        self.update_gui("Outline")
+        self.clear_text()
+        if len(path) != 0:
+            spath = ""
+            for item in path:
+                spath += "/" + str(item)
+            self.insert_text("Path {} not found\n\n".format(spath))
+        self.insert_text("Select from outline\n")
+        if self.sim is not None:
+            def tst_img():
+                self.curr_main = 1
+                self.main_image = tkinter.PhotoImage(\
+                    file = "img/splash.gif")
+                self.curr_width = self.main_image.width()
+                self.curr_height = self.main_image.height()
+                self.update_gui()
+            def tst_info():
+                self.change_gui(0, 99)
+            acts = [
+                ("Parts ({})".format(len(self.sim.parts)), ["parts"]),
+                ("Resources ({})".format(len(self.sim.res)), ["res"]),
+                ("Objects ({})".format(len(self.sim.objects)), ["objs"]),
+                ("Scenes ({})".format(len(self.sim.scenes)), ["scenes"]),
+                ("Names ({})".format(len(self.sim.names)), ["names"]),
+                ("Invntr ({})".format(len(self.sim.invntr)), ["invntr"]),
+                ("-", None),
+                ("Test image", ["tst_image"]),
+                ("Test info", ["tst_info"]),
+            ]
+            for name, act in acts:
+                self.insert_lb_act(name, act)
+
+    def path_parts(self, path):
+        self.curr_main = 0
+        if len(self.last_path) == 0 or self.last_path[0] != "parts":
+            self.update_gui("Parts ({})".format(len(self.sim.parts)))
+            for idx, name in enumerate(self.sim.parts):
+                self.insert_lb_act(name, ["parts", idx])
+        # change                
+        if len(path) > 1:
+            # parts
+            try:
+                part_id = self.curr_lb.curselection()[0]
+                part_id = int(part_id)
+            except:
+                pass
+            part_id = self.sim.parts[part_id]
+            # parse
+            pnum = part_id[5:]
+            cnum = pnum.split("Chapter", 1)
+            if len(cnum) > 1:
+                pnum = int(cnum[0].strip(), 10)
+                cnum = int(cnum[1].strip(), 10)
+            else:
+                cnum = 0
+            self.sim.open_part(pnum, cnum)
+        # display
+        self.clear_text()
+        self.insert_text("Current: part {} chapter {}\n\n  Resources: ".\
+                format(self.sim.curr_part, self.sim.curr_chap))
+        self.insert_text("{}".format(len(self.sim.res)), ["res"])
+        self.insert_text("\n  Objects:   ")
+        self.insert_text("{}".format(len(self.sim.objects)), ["objs"])
+        self.insert_text("\n  Scenes:    ")
+        self.insert_text("{}".format(len(self.sim.scenes)), ["scenes"])
+        self.insert_text("\n  Names:     ")
+        self.insert_text("{}".format(len(self.sim.names)), ["names"])
+        self.insert_text("\n  Invntr:    ")
+        self.insert_text("{}".format(len(self.sim.invntr)), ["invntr"])
+            # 
+
     def on_open_data(self):
         # open data - select TODO
         pass
         
-    def on_outline(self):
-        self.change_gui(0, 0)
-        
-    def on_list_parts(self):
-        self.change_gui(0, 90)
-
-    def on_list_res(self):
-        self.change_gui(0, 100)
-
-    def on_list_objs(self):
-        self.change_gui(0, 101)
-
-    def on_list_scenes(self):
-        self.change_gui(0, 102)
-
-    def on_list_names(self):
-        self.change_gui(0, 103)
-
-    def on_list_invntr(self):
-        self.change_gui(0, 104)
-        
     def open_data_from(self, folder):
         self.sim = petka.Engine()
         self.sim.load_data(folder, "cp1251")


Commit: 9b4d47482c734a6ef77f9470dde18cd081888059
    https://github.com/scummvm/scummvm-tools/commit/9b4d47482c734a6ef77f9470dde18cd081888059
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Refactor: objects, scenes, parts workss again

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 000fb611d..ef56d013e 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -123,6 +123,8 @@ class App(tkinter.Frame):
         
         # bind path handlers
         self.path_handler["parts"] = self.path_parts
+        self.path_handler["objs"] = self.path_objs_scenes
+        self.path_handler["scenes"] = self.path_objs_scenes
         
         self.update_after()
         self.open_path([])
@@ -469,43 +471,16 @@ class App(tkinter.Frame):
             stdinfo()
         else:
             stdinfo()
-            
-    def open_gui_elem(self, main, mode, idx):
-        if self.curr_mode != mode:
-            self.change_gui(main, mode)
-        self.curr_lb.selection_set(idx)
-        self.curr_lb.see(idx)
-        self.on_left_listbox(None)
-            
-    def open_object(self, obj_id):
-        for idx, obj in enumerate(self.sim.objects):
-            if obj.idx == obj_id:
-                self.open_gui_elem(0, 101, idx)
-                break
-
-    def open_scene(self, scn_id):
-        for idx, obj in enumerate(self.sim.scenes):
-            if obj.idx == scn_id:
-                self.open_gui_elem(0, 102, idx)
-                break
-
-    def open_name(self, name_key):
-        for idx, key in enumerate(self.sim.namesord):
-            if key == name_key:
-                self.open_gui_elem(0, 103, idx)
-                break
-
-    def open_invntr(self, inv_key):
-        for idx, key in enumerate(self.sim.invntrord):
-            if key == inv_key:
-                self.open_gui_elem(0, 104, idx)
-                break
-                
-    def change_gui(self, main, mode, sub = None):
-        self.curr_main = main
-        self.curr_mode = mode
-        self.curr_mode_sub = sub
-        self.update_gui()
+
+    def select_lb_item(self, idx):
+        try:
+            num = self.curr_lb.curselection()[0]
+            num = int(num)
+        except:
+            num = -1
+        if idx != num:
+            self.curr_lb.selection_set(idx)
+            self.curr_lb.see(idx)
 
     def on_left_listbox(self, event):
         def currsel():
@@ -517,37 +492,7 @@ class App(tkinter.Frame):
             return num
 
         def objinfo(tp, rec):
-            self.insert_text(("Object" if tp else "Scene") + ":\n")
-            self.insert_text("  Index: {}\n  Name:  {}\n".\
-                format(rec.idx, rec.name))
-            if rec.name in self.sim.names:
-                self.insert_text("  ")
-                def make_cb(key):
-                    def cb():
-                        self.open_name(key)
-                    return cb
-                self.insert_text("Alias", make_cb(rec.name))
-                self.insert_text(":  {}\n".format(self.sim.names[rec.name]))
-            if rec.name in self.sim.invntr:
-                self.insert_text("  ")
-                def make_cb(key):
-                    def cb():
-                        self.open_invntr(key)
-                    return cb
-                self.insert_text("Invntr", make_cb(rec.name))
-                self.insert_text(": {}\n".format(self.sim.invntr[rec.name]))
-                    
-            if not tp:
-                if len(rec.refs) == 0:
-                    self.insert_text("\nNo references\n")
-                else:
-                    self.insert_text("\nReferences: {}\n".format(len(rec.refs)))
-                for idx, ref in enumerate(rec.refs):
-                    self.insert_text("  {}) ".format(idx))
-                    self.insert_text("obj_{}".format(ref[0].idx), \
-                        self.make_obj_cb(ref[0].idx))
-                    self.insert_text("\n".format(idx))
-                    
+            pass                    
         if self.curr_lb_acts:
             act = self.curr_lb_acts[currsel()]
             if act[1]:
@@ -619,6 +564,30 @@ class App(tkinter.Frame):
                     self.insert_text("{} - {}".format(obj.idx, obj.name), \
                         make_cb(obj.idx))
 
+    def find_path_obj(self, obj_idx):
+        for idx, rec in enumerate(self.sim.objects):
+            if rec.idx == obj_idx:
+                return ["objs", idx]
+        return ["no_obj", obj_idx]
+
+    def find_path_scene(self, scn_idx):
+        for idx, rec in enumerate(self.sim.scenes):
+            if rec.idx == scn_idx:
+                return ["scenes", idx]
+        return ["no_scene", scn_idx]
+        
+    def find_path_name(self, key):
+        for idx, name in enumerate(self.sim.names):
+            if name == key:
+                return ["names", idx]
+        return ["no_name", key]
+
+    def find_path_invntr(self, key):
+        for idx, name in enumerate(self.sim.invntr):
+            if name == key:
+                return ["invntr", idx]
+        return ["no_invntr", key]
+
     def path_default(self, path):
         self.curr_main = 0
         self.update_gui("Outline")
@@ -662,12 +631,8 @@ class App(tkinter.Frame):
         # change                
         if len(path) > 1:
             # parts
-            try:
-                part_id = self.curr_lb.curselection()[0]
-                part_id = int(part_id)
-            except:
-                pass
-            part_id = self.sim.parts[part_id]
+            self.select_lb_item(path[1])
+            part_id = self.sim.parts[path[1]]
             # parse
             pnum = part_id[5:]
             cnum = pnum.split("Chapter", 1)
@@ -690,7 +655,58 @@ class App(tkinter.Frame):
         self.insert_text("{}".format(len(self.sim.names)), ["names"])
         self.insert_text("\n  Invntr:    ")
         self.insert_text("{}".format(len(self.sim.invntr)), ["invntr"])
-            # 
+
+    def path_objs_scenes(self, path):
+        self.curr_main = 0
+        isobj = (self.curr_path[0] == "objs")
+        if isobj:
+            lst = self.sim.objects
+        else:
+            lst = self.sim.scenes
+        if len(self.last_path) == 0 or self.last_path[0] != self.curr_path[0]:
+            if isobj:
+                self.update_gui("Objects ({})".format(len(lst)))
+            else:
+                self.update_gui("Scenes ({})".format(len(lst)))
+            for idx, rec in enumerate(lst):
+                self.insert_lb_act("{} - {}".format(rec.idx, rec.name), \
+                    [self.curr_path[0], idx])
+        # change                
+        rec = None
+        if len(path) > 1:
+            # index
+            self.select_lb_item(path[1])
+            rec = lst[path[1]]
+        # display
+        self.clear_text()
+        if not rec:
+            self.insert_text("Select item from list\n")
+        else:
+            # record info
+            self.insert_text(("Object" if isobj else "Scene") + ":\n")
+            self.insert_text("  Index: {}\n  Name:  {}\n".\
+                format(rec.idx, rec.name))
+            if rec.name in self.sim.names:
+                self.insert_text("  ")
+                self.insert_text("Alias", self.find_path_name(rec.name))
+                self.insert_text(":  {}\n".format(self.sim.names[rec.name]))
+            if rec.name in self.sim.invntr:
+                self.insert_text("  ")
+                self.insert_text("Invntr", self.find_path_invntr(rec.name))
+                self.insert_text(": {}\n".format(self.sim.invntr[rec.name]))
+                    
+            if not isobj:
+                if len(rec.refs) == 0:
+                    self.insert_text("\nNo references\n")
+                else:
+                    self.insert_text("\nReferences: {}\n".format(len(rec.refs)))
+                for idx, ref in enumerate(rec.refs):
+                    self.insert_text("  {}) ".format(idx))
+                    self.insert_text("obj_{}".format(ref[0].idx), \
+                        self.find_path_obj(ref[0].idx))
+                    self.insert_text("\n".format(idx))
+
+
 
     def on_open_data(self):
         # open data - select TODO


Commit: 4b7bd0f01682e4f2d31bdf9d614a9ea130064714
    https://github.com/scummvm/scummvm-tools/commit/4b7bd0f01682e4f2d31bdf9d614a9ea130064714
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Refactor: names and invntr works

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index ef56d013e..9c74eddda 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -125,6 +125,8 @@ class App(tkinter.Frame):
         self.path_handler["parts"] = self.path_parts
         self.path_handler["objs"] = self.path_objs_scenes
         self.path_handler["scenes"] = self.path_objs_scenes
+        self.path_handler["names"] = self.path_names
+        self.path_handler["invntr"] = self.path_invntr
         
         self.update_after()
         self.open_path([])
@@ -464,11 +466,7 @@ class App(tkinter.Frame):
                     return cb
                 self.insert_text(ft, make_cb(ft))
                 self.insert_text(": {}\n".format(fts[ft]))
-                
-        elif self.curr_mode == 101:
-            stdinfo()
-        elif self.curr_mode == 102:
-            stdinfo()
+
         else:
             stdinfo()
 
@@ -521,48 +519,7 @@ class App(tkinter.Frame):
                 self.curr_main = 1
                 self.update_gui()
             print(fn)
-        elif self.curr_mode == 101:
-            # objects
-            self.update_info()
-            objinfo(True, self.sim.objects[currsel()])
-        elif self.curr_mode == 102:
-            # scenes
-            self.update_info()
-            objinfo(False, self.sim.scenes[currsel()])
-        elif self.curr_mode == 103:
-            # names
-            self.update_info()
-            key = self.sim.namesord[currsel()]
-            self.insert_text("Alias: {}\n".format(key))
-            self.insert_text("Value: {}\n\n".format(self.sim.names[key]))
-            # search for objects
-            self.insert_text("Applied for:")
-            for obj in self.sim.objects:
-                if obj.name == key:
-                    self.insert_text("\n  ")
-                    def make_cb(idx):
-                        def cb():
-                            self.open_object(idx)
-                        return cb
-                    self.insert_text("{} - {}".format(obj.idx, obj.name), \
-                        make_cb(obj.idx))
-        elif self.curr_mode == 104:
-            # invntr
-            self.update_info()
-            key = self.sim.invntrord[currsel()]
-            self.insert_text("Invntr: {}\n".format(key))
-            self.insert_text("{}\n\n".format(self.sim.invntr[key]))
-            # search for objects
-            self.insert_text("Applied for:")
-            for obj in self.sim.objects:
-                if obj.name == key:
-                    self.insert_text("\n  ")
-                    def make_cb(idx):
-                        def cb():
-                            self.open_object(idx)
-                        return cb
-                    self.insert_text("{} - {}".format(obj.idx, obj.name), \
-                        make_cb(obj.idx))
+
 
     def find_path_obj(self, obj_idx):
         for idx, rec in enumerate(self.sim.objects):
@@ -577,13 +534,13 @@ class App(tkinter.Frame):
         return ["no_scene", scn_idx]
         
     def find_path_name(self, key):
-        for idx, name in enumerate(self.sim.names):
+        for idx, name in enumerate(self.sim.namesord):
             if name == key:
                 return ["names", idx]
         return ["no_name", key]
 
     def find_path_invntr(self, key):
-        for idx, name in enumerate(self.sim.invntr):
+        for idx, name in enumerate(self.sim.invntrord):
             if name == key:
                 return ["invntr", idx]
         return ["no_invntr", key]
@@ -695,18 +652,83 @@ class App(tkinter.Frame):
                 self.insert_text("Invntr", self.find_path_invntr(rec.name))
                 self.insert_text(": {}\n".format(self.sim.invntr[rec.name]))
                     
-            if not isobj:
+            if isobj:
+                # search where object used
+                self.insert_text("\nUsed in:\n")
+                for scn in self.sim.scenes:
+                    for ref in scn.refs:
+                        if ref[0].idx == rec.idx:
+                            self.insert_text("  ")
+                            self.insert_text("{}".format(scn.idx), \
+                                self.find_path_scene(scn.idx))
+                            self.insert_text(" - {}\n".format(scn.name))
+                            break
+            else:
                 if len(rec.refs) == 0:
                     self.insert_text("\nNo references\n")
                 else:
                     self.insert_text("\nReferences: {}\n".format(len(rec.refs)))
                 for idx, ref in enumerate(rec.refs):
                     self.insert_text("  {}) ".format(idx))
-                    self.insert_text("obj_{}".format(ref[0].idx), \
+                    self.insert_text("{}".format(ref[0].idx), \
                         self.find_path_obj(ref[0].idx))
-                    self.insert_text("\n".format(idx))
-
+                    self.insert_text(" - {}\n".format(ref[0].name))
 
+    def path_names(self, path):
+        self.curr_main = 0
+        if len(self.last_path) == 0 or self.last_path[0] != "names":
+            self.update_gui("Names ({})".format(len(self.sim.names)))
+            for idx, name in enumerate(self.sim.namesord):
+                self.insert_lb_act(name, ["names", idx])
+        # change
+        name = None
+        if len(path) > 1:
+            # parts
+            self.select_lb_item(path[1])
+            name = self.sim.namesord[path[1]]
+        # display
+        self.clear_text()
+        if not name:
+            self.insert_text("Select name from list\n")
+        else:
+            # name info
+            self.insert_text("Alias: {}\n".format(name))
+            self.insert_text("Value: {}\n\n".format(self.sim.names[name]))
+            # search for objects
+            self.insert_text("Applied for:\n")
+            for idx, obj in enumerate(self.sim.objects):
+                if obj.name == name:
+                    self.insert_text("  ")
+                    self.insert_text("{}".format(obj.idx), ["objs", idx])
+                    self.insert_text(" - {}\n".format(obj.name))
+
+    def path_invntr(self, path):
+        self.curr_main = 0
+        if len(self.last_path) == 0 or self.last_path[0] != "invntr":
+            self.update_gui("Invntr ({})".format(len(self.sim.invntr)))
+            for idx, name in enumerate(self.sim.invntrord):
+                self.insert_lb_act(name, ["invntr", idx])
+        # change
+        name = None
+        if len(path) > 1:
+            # parts
+            self.select_lb_item(path[1])
+            name = self.sim.invntrord[path[1]]
+        # display
+        self.clear_text()
+        if not name:
+            self.insert_text("Select invntr from list\n")
+        else:
+            # invntr info
+            self.insert_text("Invntr: {}\n".format(name))
+            self.insert_text("{}\n\n".format(self.sim.invntr[name]))
+            # search for objects
+            self.insert_text("Applied for:\n")
+            for idx, obj in enumerate(self.sim.objects):
+                if obj.name == name:
+                    self.insert_text("  ")
+                    self.insert_text("{}".format(obj.idx), ["objs", idx])
+                    self.insert_text(" - {}\n".format(obj.name))
 
     def on_open_data(self):
         # open data - select TODO


Commit: 0c54fa10a25d67c70730e0b1c5e7a51452a2ed8d
    https://github.com/scummvm/scummvm-tools/commit/0c54fa10a25d67c70730e0b1c5e7a51452a2ed8d
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Refactor: names and invntr works

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 9c74eddda..3c2afae8f 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -123,10 +123,12 @@ class App(tkinter.Frame):
         
         # bind path handlers
         self.path_handler["parts"] = self.path_parts
+        self.path_handler["res"] = self.path_res
         self.path_handler["objs"] = self.path_objs_scenes
         self.path_handler["scenes"] = self.path_objs_scenes
         self.path_handler["names"] = self.path_names
         self.path_handler["invntr"] = self.path_invntr
+        self.path_handler["test"] = self.path_test
         
         self.update_after()
         self.open_path([])
@@ -380,28 +382,6 @@ class App(tkinter.Frame):
                         ("." + self.curr_mode_sub):
                         lb.insert(tkinter.END, "{} - {}".format(res_id, \
                             self.sim.res[res_id]))
-        elif self.curr_mode == 101:
-            # list objects
-            lb = self.update_gui_add_left_listbox("Objects")
-            for obj in self.sim.objects:
-                lb.insert(tkinter.END, "{} - {}".format(obj.idx, obj.name))
-        elif self.curr_mode == 102:
-            # list scenes
-            lb = self.update_gui_add_left_listbox("Scenes")
-            for scn in self.sim.scenes:
-                lb.insert(tkinter.END, "{} - {}".format(scn.idx, scn.name))
-        elif self.curr_mode == 103:
-            # list names
-            lb = self.update_gui_add_left_listbox("Names")
-            for name in self.sim.namesord:
-                lb.insert(tkinter.END, "{}".format(name))
-        elif self.curr_mode == 104:
-            # list invntr
-            lb = self.update_gui_add_left_listbox("Invntr")
-            for name in self.sim.invntrord:
-                lb.insert(tkinter.END, "{}".format(name))
-        self.update_info()
-        self.update_after()
 
     def clear_text(self):
         self.text_view.delete(0.0, tkinter.END)


Commit: 6cad232811e7102869c8d81ca824be9400176a39
    https://github.com/scummvm/scummvm-tools/commit/6cad232811e7102869c8d81ca824be9400176a39
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Refactor: test image and info panel

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 3c2afae8f..28063d81a 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -473,7 +473,7 @@ class App(tkinter.Frame):
             pass                    
         if self.curr_lb_acts:
             act = self.curr_lb_acts[currsel()]
-            if act[1]:
+            if act[1] is not None:
                 self.open_path(act[1])
         return
         
@@ -536,15 +536,6 @@ class App(tkinter.Frame):
             self.insert_text("Path {} not found\n\n".format(spath))
         self.insert_text("Select from outline\n")
         if self.sim is not None:
-            def tst_img():
-                self.curr_main = 1
-                self.main_image = tkinter.PhotoImage(\
-                    file = "img/splash.gif")
-                self.curr_width = self.main_image.width()
-                self.curr_height = self.main_image.height()
-                self.update_gui()
-            def tst_info():
-                self.change_gui(0, 99)
             acts = [
                 ("Parts ({})".format(len(self.sim.parts)), ["parts"]),
                 ("Resources ({})".format(len(self.sim.res)), ["res"]),
@@ -553,8 +544,8 @@ class App(tkinter.Frame):
                 ("Names ({})".format(len(self.sim.names)), ["names"]),
                 ("Invntr ({})".format(len(self.sim.invntr)), ["invntr"]),
                 ("-", None),
-                ("Test image", ["tst_image"]),
-                ("Test info", ["tst_info"]),
+                ("Test image", ["test", "image"]),
+                ("Test info", ["test","info"]),
             ]
             for name, act in acts:
                 self.insert_lb_act(name, act)
@@ -593,6 +584,9 @@ class App(tkinter.Frame):
         self.insert_text("\n  Invntr:    ")
         self.insert_text("{}".format(len(self.sim.invntr)), ["invntr"])
 
+    def path_res(self, path):
+        pass
+
     def path_objs_scenes(self, path):
         self.curr_main = 0
         isobj = (self.curr_path[0] == "objs")
@@ -710,6 +704,26 @@ class App(tkinter.Frame):
                     self.insert_text("{}".format(obj.idx), ["objs", idx])
                     self.insert_text(" - {}\n".format(obj.name))
 
+    def path_test(self, path):
+        if path[1] == "image":
+            self.curr_main = 1
+            self.main_image = tkinter.PhotoImage(\
+                file = "img/splash.gif")
+            self.curr_width = self.main_image.width()
+            self.curr_height = self.main_image.height()
+        else:
+            self.curr_main = 0
+        self.update_gui("Test {}".format(path[1]))
+        self.insert_lb_act("Outline", [])
+        self.insert_lb_act("-", None)
+        for i in range(15):
+            self.insert_lb_act("{} #{}".format(path[1], i), path[:2] + (i,))
+        if path[1] != "image":
+            self.clear_text()
+            for i in range(100):
+                self.insert_text("  Item {}\n".format(i))
+
+
     def on_open_data(self):
         # open data - select TODO
         pass


Commit: b7adac730204a60eac4ea19e4ede8807260ab3b8
    https://github.com/scummvm/scummvm-tools/commit/b7adac730204a60eac4ea19e4ede8807260ab3b8
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Refactor: switch info and canvas

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 28063d81a..763c7f66a 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -64,7 +64,7 @@ class App(tkinter.Frame):
         self.sim = None
         # gui
         self.path_handler = {}
-        self.curr_main = 0 # 0 - frame, 1 - canvas
+        self.curr_main = -1 # 0 - frame, 1 - canvas
         self.curr_path = []
         self.last_path = None
         self.curr_mode = 0
@@ -300,7 +300,6 @@ class App(tkinter.Frame):
     
     def update_gui(self, text = "<Undefined>"):
         self.last_path = self.curr_path
-        self.canv_view.delete(tkinter.ALL)
         # cleanup
         for item in self.curr_gui:
             item()
@@ -331,8 +330,13 @@ class App(tkinter.Frame):
         # actions on listbox
         self.curr_lb = lb
         self.curr_lb_acts = []
+
+    def switch_view(self, main):
         # main view
-        if self.curr_main == 0:
+        if main == self.curr_main: return
+        self.curr_main = main
+        if main == 0:
+            self.canv_view.delete(tkinter.ALL)
             self.canv_view.grid_forget()
             self.text_view.grid(row = 0, column = 0, \
                 sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
@@ -343,6 +347,7 @@ class App(tkinter.Frame):
             self.scr_view_x.config(command = self.text_view.xview)
             self.scr_view_y.config(command = self.text_view.yview)
         else:
+            self.canv_view.delete(tkinter.ALL)
             self.text_view.grid_forget()
             self.canv_view.grid(row = 0, column = 0, \
                 sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
@@ -352,36 +357,6 @@ class App(tkinter.Frame):
             )
             self.scr_view_x.config(command = self.canv_view.xview)
             self.scr_view_y.config(command = self.canv_view.yview)
-        return
-        
-        
-        if self.curr_mode == 0:
-            pass
-        elif self.curr_mode == 99:
-            acts = [
-                ("<- outline", self.on_outline)
-            ]
-            self.update_gui_add_left_listbox("Test info", acts)                
-        elif self.curr_mode == 90:
-            # list parts
-            lb = self.update_gui_add_left_listbox("Parts")
-            for part in self.sim.parts:
-                lb.insert(tkinter.END, part)
-        elif self.curr_mode == 100:
-            # list resources
-            if self.curr_mode_sub is None:
-                lb = self.update_gui_add_left_listbox("Resources") 
-                for res_id in self.sim.resord:
-                    lb.insert(tkinter.END, "{} - {}".format(res_id, \
-                        self.sim.res[res_id]))
-            else:
-                lb = self.update_gui_add_left_listbox("Resources: {}".\
-                    format(self.curr_mode_sub))
-                for res_id in self.sim.resord:
-                    if self.sim.res[res_id].upper().endswith\
-                        ("." + self.curr_mode_sub):
-                        lb.insert(tkinter.END, "{} - {}".format(res_id, \
-                            self.sim.res[res_id]))
 
     def clear_text(self):
         self.text_view.delete(0.0, tkinter.END)
@@ -526,7 +501,7 @@ class App(tkinter.Frame):
         return ["no_invntr", key]
 
     def path_default(self, path):
-        self.curr_main = 0
+        self.switch_view(0)
         self.update_gui("Outline")
         self.clear_text()
         if len(path) != 0:
@@ -551,7 +526,7 @@ class App(tkinter.Frame):
                 self.insert_lb_act(name, act)
 
     def path_parts(self, path):
-        self.curr_main = 0
+        self.switch_view(0)
         if len(self.last_path) == 0 or self.last_path[0] != "parts":
             self.update_gui("Parts ({})".format(len(self.sim.parts)))
             for idx, name in enumerate(self.sim.parts):
@@ -586,9 +561,23 @@ class App(tkinter.Frame):
 
     def path_res(self, path):
         pass
+        # list resources
+        if self.curr_mode_sub is None:
+            lb = self.update_gui_add_left_listbox("Resources") 
+            for res_id in self.sim.resord:
+                lb.insert(tkinter.END, "{} - {}".format(res_id, \
+                    self.sim.res[res_id]))
+        else:
+            lb = self.update_gui_add_left_listbox("Resources: {}".\
+                format(self.curr_mode_sub))
+            for res_id in self.sim.resord:
+                if self.sim.res[res_id].upper().endswith\
+                    ("." + self.curr_mode_sub):
+                    lb.insert(tkinter.END, "{} - {}".format(res_id, \
+                        self.sim.res[res_id]))
 
     def path_objs_scenes(self, path):
-        self.curr_main = 0
+        self.switch_view(0)
         isobj = (self.curr_path[0] == "objs")
         if isobj:
             lst = self.sim.objects
@@ -649,7 +638,7 @@ class App(tkinter.Frame):
                     self.insert_text(" - {}\n".format(ref[0].name))
 
     def path_names(self, path):
-        self.curr_main = 0
+        self.switch_view(0)
         if len(self.last_path) == 0 or self.last_path[0] != "names":
             self.update_gui("Names ({})".format(len(self.sim.names)))
             for idx, name in enumerate(self.sim.namesord):
@@ -677,7 +666,7 @@ class App(tkinter.Frame):
                     self.insert_text(" - {}\n".format(obj.name))
 
     def path_invntr(self, path):
-        self.curr_main = 0
+        self.switch_view(0)
         if len(self.last_path) == 0 or self.last_path[0] != "invntr":
             self.update_gui("Invntr ({})".format(len(self.sim.invntr)))
             for idx, name in enumerate(self.sim.invntrord):
@@ -706,13 +695,13 @@ class App(tkinter.Frame):
 
     def path_test(self, path):
         if path[1] == "image":
-            self.curr_main = 1
+            self.switch_view(1)
             self.main_image = tkinter.PhotoImage(\
                 file = "img/splash.gif")
             self.curr_width = self.main_image.width()
             self.curr_height = self.main_image.height()
         else:
-            self.curr_main = 0
+            self.switch_view(0)
         self.update_gui("Test {}".format(path[1]))
         self.insert_lb_act("Outline", [])
         self.insert_lb_act("-", None)
@@ -723,7 +712,6 @@ class App(tkinter.Frame):
             for i in range(100):
                 self.insert_text("  Item {}\n".format(i))
 
-
     def on_open_data(self):
         # open data - select TODO
         pass


Commit: 72bf691b391cac206dcdb83f7bfac82c98124158
    https://github.com/scummvm/scummvm-tools/commit/72bf691b391cac206dcdb83f7bfac82c98124158
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Refactor: resources works (filter and all list)

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 763c7f66a..9e34673ac 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -7,6 +7,7 @@ import sys, os
 import tkinter
 from tkinter import ttk, font
 from idlelib.WidgetRedirector import WidgetRedirector
+import traceback
 
 import petka
 
@@ -292,12 +293,7 @@ class App(tkinter.Frame):
         image = tkinter.PhotoImage(width = width, height = height, \
             data = bytes(p))
         return image                
-
-    def make_obj_cb(self, idx):
-        def cb():
-            self.open_object(idx)
-        return cb
-    
+   
     def update_gui(self, text = "<Undefined>"):
         self.last_path = self.curr_path
         # cleanup
@@ -379,52 +375,6 @@ class App(tkinter.Frame):
         self.curr_lb_acts.append((name, act))
         self.curr_lb.insert(tkinter.END, name)
 
-    def update_info(self):
-        def stdinfo():
-            self.text_view.delete(0.0, tkinter.END)
-            self.insert_text("<- Outline", self.on_outline)
-            self.insert_text("\n\n")
-
-        self.text_hl.reset()
-        if self.curr_mode == 0:
-            self.text_view.delete(0.0, tkinter.END)
-            if self.sim is None:
-                self.insert_text("No data loaded")
-                self.insert_text("Open data",self.on_open_data)
-            else:
-                self.insert_text("Select type from outline")
-        elif self.curr_mode == 99:
-            stdinfo()
-            for i in range(100):
-                self.insert_text("Item {}\n".format(i))
-        elif self.curr_mode == 90:
-            stdinfo()
-        elif self.curr_mode == 100:
-            stdinfo()
-            self.insert_text("Total: ")
-            self.insert_text("{}".format(len(self.sim.res)), \
-                lambda: self.change_gui(0, 100))
-            self.insert_text("\nFiletypes:\n")
-            fts = {}
-            for res in self.sim.res.values():
-                fp = res.rfind(".")
-                if fp >= 0:
-                    ft = res[fp + 1:].upper()
-                    fts[ft] = fts.get(ft, 0) + 1
-            ftk = list(fts.keys())
-            ftk.sort()
-            for ft in ftk:
-                self.insert_text("  ")
-                def make_cb(key):
-                    def cb():
-                        self.change_gui(0, 100, key)
-                    return cb
-                self.insert_text(ft, make_cb(ft))
-                self.insert_text(": {}\n".format(fts[ft]))
-
-        else:
-            stdinfo()
-
     def select_lb_item(self, idx):
         try:
             num = self.curr_lb.curselection()[0]
@@ -444,36 +394,10 @@ class App(tkinter.Frame):
                 pass
             return num
 
-        def objinfo(tp, rec):
-            pass                    
         if self.curr_lb_acts:
             act = self.curr_lb_acts[currsel()]
             if act[1] is not None:
                 self.open_path(act[1])
-        return
-        
-        if self.curr_mode == 90:
-            pass
-        elif self.curr_mode == 100:
-            # resources
-            try:
-                res_id = self.curr_lb.curselection()[0]
-                res_id = self.curr_lb.get(res_id).split("-", 1)[0].strip()
-                res_id = int(res_id)
-            except:
-                pass
-            fn = self.sim.res[res_id]
-            if fn[-4:].lower() == ".bmp":
-                bmpdata = self.sim.fman.read_file(fn)
-                bmp = petka.BMPLoader()
-                bmp.load_data(bmpdata)
-                self.main_image = \
-                    self.make_image(bmp.width, bmp.height, bmp.rgb)
-                self.curr_width = bmp.width
-                self.curr_height = bmp.height
-                self.curr_main = 1
-                self.update_gui()
-            print(fn)
 
 
     def find_path_obj(self, obj_idx):
@@ -527,7 +451,7 @@ class App(tkinter.Frame):
 
     def path_parts(self, path):
         self.switch_view(0)
-        if len(self.last_path) == 0 or self.last_path[0] != "parts":
+        if self.last_path[:1] != ("parts",):
             self.update_gui("Parts ({})".format(len(self.sim.parts)))
             for idx, name in enumerate(self.sim.parts):
                 self.insert_lb_act(name, ["parts", idx])
@@ -560,6 +484,31 @@ class App(tkinter.Frame):
         self.insert_text("{}".format(len(self.sim.invntr)), ["invntr"])
 
     def path_res(self, path):
+        # res - full list
+        # res/flt/<ext> - list by <ext>
+        # res/all/<id> - display res by id
+        if path == ("res",):
+            path = ("res", "all")
+        if path[1] == "flt":
+            return self.path_res_flt(path)
+        elif path[1] == "all":
+            return self.path_res_all(path)
+        else:
+            return self.path_default()
+            
+            
+        if self.last_path[:2] != path[:2]:
+            # 
+            self.switch_view(0)
+            self.update_gui("Resources: {}".format(path[2]))
+            for idx,res_id in self.sim.resord:
+                if self.sim.res[res_id].upper().endswith("." + path[2]):
+                    self.insert_lb_act("{} - {}".format(res_id))
+        
+        if len(self.last_path) == 0 or self.last_path[0] != "res":
+            for idx, name in enumerate(self.sim.invntrord):
+                self.insert_lb_act(name, ["invntr", idx])
+
         pass
         # list resources
         if self.curr_mode_sub is None:
@@ -576,6 +525,83 @@ class App(tkinter.Frame):
                     lb.insert(tkinter.END, "{} - {}".format(res_id, \
                         self.sim.res[res_id]))
 
+    def path_res_open(self, res_id):
+        fn = self.sim.res[res_id]
+        if fn[-4:].lower() == ".bmp":
+            try:
+                bmpdata = self.sim.fman.read_file(fn)
+                bmp = petka.BMPLoader()
+                bmp.load_data(bmpdata)
+                self.main_image = \
+                    self.make_image(bmp.width, bmp.height, bmp.rgb)
+                self.curr_width = bmp.width
+                self.curr_height = bmp.height
+                self.switch_view(1)
+                self.update_canvas()
+            except:
+                self.switch_view(0)
+                self.clear_text()
+                self.insert_text("Error loading {} - \"{}\" \n\n{}".\
+                    format(res_id, fn, traceback.format_exc()))
+        else:
+            self.switch_view(0)
+            self.clear_text()
+            self.insert_text("Resource {} - \"{}\" cannot be displayed\n".\
+                format(res_id, fn))
+
+    def path_res_status(self):
+        self.switch_view(0)
+        self.clear_text()
+        self.insert_text("Total: ")
+        self.insert_text("{}".format(len(self.sim.res)), ["res"])
+        self.insert_text("\nFiletypes:\n")
+        fts = {}
+        for res in self.sim.res.values():
+            fp = res.rfind(".")
+            if fp >= 0:
+                ft = res[fp + 1:].upper()
+                fts[ft] = fts.get(ft, 0) + 1
+        ftk = list(fts.keys())
+        ftk.sort()
+        for ft in ftk:
+            self.insert_text("  ")
+            self.insert_text(ft, ["res", "flt", ft])
+            self.insert_text(": {}\n".format(fts[ft]))
+
+    def path_res_all(self, path):
+        if self.last_path[:2] != ("res", "all",):
+            self.update_gui("Resources ({})".format(len(self.sim.res)))
+            for idx, res_id in enumerate(self.sim.resord):
+                    self.insert_lb_act("{} - {}".format(\
+                res_id, self.sim.res[res_id]), ["res", "all", idx])
+        # change                
+        if len(path) > 2:
+            # parts
+            self.select_lb_item(path[2])
+            res_id = self.sim.resord[path[2]]
+            self.path_res_open(res_id)
+        else:
+            self.path_res_status()
+
+    def path_res_flt(self, path):
+        lst = []
+        for idx, res_id in enumerate(self.sim.resord):
+            if self.sim.res[res_id].upper().endswith("." + path[2]):
+                lst.append(res_id)
+        if self.last_path[:3] != ("res", "flt", path[2]):
+            self.update_gui("Resources {} ({})".format(path[2], len(lst)))
+            for idx, res_id in enumerate(lst):
+                    self.insert_lb_act("{} - {}".format(\
+                res_id, self.sim.res[res_id]), ["res", "flt", path[2], idx])
+        # change                
+        if len(path) > 3:
+            # parts
+            self.select_lb_item(path[3])
+            res_id = lst[path[3]]
+            self.path_res_open(res_id)
+        else:
+            self.path_res_status()
+
     def path_objs_scenes(self, path):
         self.switch_view(0)
         isobj = (self.curr_path[0] == "objs")
@@ -583,7 +609,7 @@ class App(tkinter.Frame):
             lst = self.sim.objects
         else:
             lst = self.sim.scenes
-        if len(self.last_path) == 0 or self.last_path[0] != self.curr_path[0]:
+        if self.last_path[:1] != (self.curr_path[0],):
             if isobj:
                 self.update_gui("Objects ({})".format(len(lst)))
             else:
@@ -639,7 +665,7 @@ class App(tkinter.Frame):
 
     def path_names(self, path):
         self.switch_view(0)
-        if len(self.last_path) == 0 or self.last_path[0] != "names":
+        if self.last_path[:1] != ("names",):
             self.update_gui("Names ({})".format(len(self.sim.names)))
             for idx, name in enumerate(self.sim.namesord):
                 self.insert_lb_act(name, ["names", idx])
@@ -667,7 +693,7 @@ class App(tkinter.Frame):
 
     def path_invntr(self, path):
         self.switch_view(0)
-        if len(self.last_path) == 0 or self.last_path[0] != "invntr":
+        if self.last_path[:1] != ("invntr",):
             self.update_gui("Invntr ({})".format(len(self.sim.invntr)))
             for idx, name in enumerate(self.sim.invntrord):
                 self.insert_lb_act(name, ["invntr", idx])
@@ -694,6 +720,11 @@ class App(tkinter.Frame):
                     self.insert_text(" - {}\n".format(obj.name))
 
     def path_test(self, path):
+        self.update_gui("Test {}".format(path[1]))
+        self.insert_lb_act("Outline", [])
+        self.insert_lb_act("-", None)
+        for i in range(15):
+            self.insert_lb_act("{} #{}".format(path[1], i), path[:2] + (i,))
         if path[1] == "image":
             self.switch_view(1)
             self.main_image = tkinter.PhotoImage(\
@@ -702,13 +733,8 @@ class App(tkinter.Frame):
             self.curr_height = self.main_image.height()
         else:
             self.switch_view(0)
-        self.update_gui("Test {}".format(path[1]))
-        self.insert_lb_act("Outline", [])
-        self.insert_lb_act("-", None)
-        for i in range(15):
-            self.insert_lb_act("{} #{}".format(path[1], i), path[:2] + (i,))
-        if path[1] != "image":
             self.clear_text()
+            self.insert_text("Information panel for {}\n".format(path))
             for i in range(100):
                 self.insert_text("  Item {}\n".format(i))
 


Commit: 4f62901edd222c465d200debb43bb91753405fa0
    https://github.com/scummvm/scummvm-tools/commit/4f62901edd222c465d200debb43bb91753405fa0
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Refactor image loading

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/__init__.py
    engines/petka/petka/engine.py
    engines/petka/petka/imgbmp.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 9e34673ac..82269d835 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -67,7 +67,7 @@ class App(tkinter.Frame):
         self.path_handler = {}
         self.curr_main = -1 # 0 - frame, 1 - canvas
         self.curr_path = []
-        self.last_path = None
+        self.last_path = [None]
         self.curr_mode = 0
         self.curr_mode_sub = None
         self.curr_gui = []
@@ -132,7 +132,7 @@ class App(tkinter.Frame):
         self.path_handler["test"] = self.path_test
         
         self.update_after()
-        self.open_path([])
+        self.open_path(self.find_path_scene(36))
 
     def create_menu(self):
         self.menubar = tkinter.Menu(self.master)
@@ -399,6 +399,11 @@ class App(tkinter.Frame):
             if act[1] is not None:
                 self.open_path(act[1])
 
+    def find_path_res(self, res):
+        for idx, res_id in enumerate(self.sim.resord):
+            if res_id == res:
+                return ["res", "all", idx]
+        return ["no_res", res]
 
     def find_path_obj(self, obj_idx):
         for idx, rec in enumerate(self.sim.objects):
@@ -529,9 +534,9 @@ class App(tkinter.Frame):
         fn = self.sim.res[res_id]
         if fn[-4:].lower() == ".bmp":
             try:
-                bmpdata = self.sim.fman.read_file(fn)
+                bmpf = self.sim.fman.read_file_stream(fn)
                 bmp = petka.BMPLoader()
-                bmp.load_data(bmpdata)
+                bmp.load_data(bmpf)
                 self.main_image = \
                     self.make_image(bmp.width, bmp.height, bmp.rgb)
                 self.curr_width = bmp.width
@@ -543,6 +548,8 @@ class App(tkinter.Frame):
                 self.clear_text()
                 self.insert_text("Error loading {} - \"{}\" \n\n{}".\
                     format(res_id, fn, traceback.format_exc()))
+            finally:
+                bmpf.close()
         else:
             self.switch_view(0)
             self.clear_text()
@@ -640,7 +647,8 @@ class App(tkinter.Frame):
                 self.insert_text("  ")
                 self.insert_text("Invntr", self.find_path_invntr(rec.name))
                 self.insert_text(": {}\n".format(self.sim.invntr[rec.name]))
-                    
+
+            # references / backreferences                    
             if isobj:
                 # search where object used
                 self.insert_text("\nUsed in:\n")
@@ -661,7 +669,57 @@ class App(tkinter.Frame):
                     self.insert_text("  {}) ".format(idx))
                     self.insert_text("{}".format(ref[0].idx), \
                         self.find_path_obj(ref[0].idx))
-                    self.insert_text(" - {}\n".format(ref[0].name))
+                    msg = ""
+                    for arg in ref[1:]:
+                        msg += " "
+                        if arg < 10:
+                            msg += "{}".format(arg)
+                        elif arg == 0xffffffff:
+                            msg += "-1"
+                        else:
+                            msg += "0x{:X}".format(arg)
+                    self.insert_text(msg + " / {}\n".format(ref[0].name))
+
+            resused = []
+            self.insert_text("\nHandlers: {}\n".format(len(rec.acts)))
+            for idx, (act_id, act_cond, act_arg, ops) in enumerate(rec.acts):
+                msg = petka.OPCODES.get(act_id, ["OP_{:X}".format(act_id)])[0]
+                if act_cond != 0xff or act_arg != 0xffff:
+                    msg += " 0x{:02X} 0x{:04X}".format(act_cond, act_arg)
+                self.insert_text("  {}) on {}, ops: {}\n".format(\
+                    idx, msg, len(ops)))
+                for oidx, op in enumerate(ops):
+                    msg = petka.OPCODES.get(op[1], ["OP_{:X}".format(op[1])])[0]
+                    self.insert_text("    {}) {} ".format(oidx, msg))
+                    if op[0] == rec.idx:
+                        self.insert_text("THIS")
+                    else:
+                        self.insert_text("{}".format(op[0]), \
+                            self.find_path_obj(op[0]))
+                    msg = ""
+                    if op[2] != 0xffff:
+                        if op[2] not in resused and op[2] in self.sim.res:
+                            resused.append(op[2])
+                    for arg in op[2:]:
+                        msg += " "
+                        if arg < 10:
+                            msg += "{}".format(arg)
+                        elif arg == 0xffff:
+                            msg += "-1"
+                        else:
+                            msg += "0x{:X}".format(arg)
+                    self.insert_text("{}\n".format(msg))
+                    
+            if len(resused) > 0:
+                self.insert_text("\nUsed resources: {}\n".format(len(resused)))
+                for res_id in resused:
+                    self.insert_text("  ")
+                    self.insert_text("{}".format(res_id), \
+                        self.find_path_res(res_id))
+                    self.insert_text(" - {} (0x{:X})\n".\
+                        format(self.sim.res[res_id], res_id))
+                
+            
 
     def path_names(self, path):
         self.switch_view(0)
diff --git a/engines/petka/petka/__init__.py b/engines/petka/petka/__init__.py
index ecf231162..712beca4a 100644
--- a/engines/petka/petka/__init__.py
+++ b/engines/petka/petka/__init__.py
@@ -1,6 +1,6 @@
 
 class EngineError(Exception): pass
 
-from .engine import Engine
+from .engine import Engine, OPCODES
 from .fman import FileManager
 from .imgbmp import BMPLoader
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 9c4a34335..d6fc1b189 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -8,6 +8,67 @@ import io
 
 from .fman import FileManager
 
+OPCODES = {
+    1:  ("USE",         0),
+    2:  ("SETPOS",      2),
+    3:  ("GOTO",        0),
+    4:  ("LOOK",        0),
+    5:  ("SAY",         0),
+    6:  ("TAKE",        0),
+    9:  ("WALK",        2),
+    10: ("TALK",        0),
+    11: ("END",         0),
+    14: ("SET",         1),
+    15: ("SHOW",        1),
+    16: ("HIDE",        0),
+    17: ("DIALOG",      1),
+    18: ("ZBUFFER",     0),
+    19: ("TOTALINIT",   1),
+    20: ("ANIMATE",     1),
+    21: ("STATUS",      1),
+    22: ("ADDINV",      0),
+    23: ("DELINV",      0),
+    24: ("STOP",        1),
+    25: ("CURSOR",      1),
+    26: ("OBJECTUSE",   0),
+    27: ("ACTIVE",      1),
+    28: ("SAID",        0),
+    29: ("SETSEQ",      0),
+    30: ("ENDSEQ",      0),
+    31: ("CHECK",       0),
+    32: ("IF",          0),
+    33: ("DESCRIPTION", 0),
+    34: ("HALF",        0),
+    36: ("WALKTO",      0),
+    37: ("WALKVICH",    0),
+    38: ("INITBG",      0),
+    39: ("USERMSG",     0),
+    40: ("SYSTEM",      0),
+    41: ("SETZBUFFER",  0),
+    42: ("CONTINUE",    0),
+    43: ("MAP",         1),
+    44: ("PASSIVE",     1),
+    45: ("NOMAP",       1),
+    46: ("SETINV",      1),
+    47: ("BGSFX",       1),
+    48: ("MUSIC",       1),
+    49: ("IMAGE",       1),
+    50: ("STAND",       1),
+    51: ("ON",          1),
+    52: ("OFF",         1),
+    53: ("PLAY",        1),
+    54: ("LEAVEBG",     0),
+    55: ("SHAKE",       1),
+    56: ("SP",          2),
+    57: ("RANDOM",      1),
+    58: ("JUMP",        0),
+    59: ("JUMPVICH",    0),
+    60: ("PART",        2),
+    61: ("CHAPTER",     2),
+    62: ("AVI",         1),
+    63: ("TOMAP",       0),
+}
+
 class ScrObject:
     def __init__(self, idx, name):
         self.idx = idx
diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
index eaf849fc3..12c098b4d 100644
--- a/engines/petka/petka/imgbmp.py
+++ b/engines/petka/petka/imgbmp.py
@@ -12,19 +12,21 @@ class BMPLoader:
         self.width = 0
         self.height = 0
         
-    def load_data(self, data):
+    def load_data(self, f):
         # TODO: normal BMP, rle BMP
         # check magic string "BM"
-        if data[:2] != b"BM":
+        temp = f.read(2)
+        if temp != b"BM":
             raise EngineError("Bad magic string")
         off = 2
         
-        f_sz, res1, res2, data_offset = struct.unpack_from("<IHHI", \
-            data[off:off + 12])
+        temp = f.read(12)
+        f_sz, res1, res2, data_offset = struct.unpack_from("<IHHI", temp)
         off += 12
         
         # read next 40 bytes, BITMAPINFOHEADER
-        pict = struct.unpack_from("<IiiHHIIiiII", data[off:off + 40])
+        temp = f.read(40)
+        pict = struct.unpack_from("<IiiHHIIiiII", temp)
         off += 40
         if pict[0] != 40:
             raise EngineError("Unsupported InfoHeader")
@@ -39,22 +41,24 @@ class BMPLoader:
             raise EngineError("To small bitmap data offset")
         if delta != 8:
             raise EngineError("Unsupported Header at 0x36")
-        hdr36 = struct.unpack_from("<II", data[off:off + delta])
+        temp = f.read(delta)
+        hdr36 = struct.unpack_from("<II", temp)
         off += delta
 
         bsz = pictw * picth * 2
-        picture_data = data[off:off + bsz]
+        picture_data = f.read(bsz)
         off += bsz
         if len(picture_data) != bsz:
             raise EngineError("Bitmap truncated, need {}, got {}".format(bsz, \
                 len(picture_data)))
 
         # read 2 zero bytes
-        if data[off:off + 2] != b"\x00\x00":
+        temp = f.read(2)
+        if temp != b"\x00\x00":
             raise EngineError("Magic zero bytes absent or mismatch")
         off += 2
 
-        if len(data) - off > 0:
+        if len(f.read()) > 0:
             raise EngineError("BMP read error, some data unparsed")
                 
         # convert 16 bit to 24


Commit: 11850e3b02eac4b4fc7d3f3881449598d48c58a5
    https://github.com/scummvm/scummvm-tools/commit/11850e3b02eac4b4fc7d3f3881449598d48c58a5
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Refactor image loading

Changed paths:
    engines/petka/petka/imgbmp.py


diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
index 12c098b4d..da3bd3b8e 100644
--- a/engines/petka/petka/imgbmp.py
+++ b/engines/petka/petka/imgbmp.py
@@ -6,13 +6,20 @@ import array, struct
 
 from . import EngineError
 
+try:
+    from PIL import Image
+except:
+    Image = None
+
 class BMPLoader:
     def __init__(self):
-        self.raw = None
+        self.rgb = None
+        self.image = None
         self.width = 0
         self.height = 0
+        self.imginfo = ""
         
-    def load_data(self, f):
+    def load_data_int(self, f):
         # TODO: normal BMP, rle BMP
         # check magic string "BM"
         temp = f.read(2)
@@ -75,3 +82,12 @@ class BMPLoader:
             off = (picth - i - 1) * pictw * 3
             self.rgb += rgb[off:off + pictw * 3]
         
+        self.imginfo = "Internal BMP loader"
+        
+    def load_data(self, f):
+        try:
+            img = Image.open(f)
+        except:
+            f.seek(0)
+            self.load_data_int(f)
+        


Commit: c86264eca669d71ac6e73163681c14a0186eed43
    https://github.com/scummvm/scummvm-tools/commit/c86264eca669d71ac6e73163681c14a0186eed43
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Initial Pillow support

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/imgbmp.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 82269d835..a365e0a4d 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -9,6 +9,15 @@ from tkinter import ttk, font
 from idlelib.WidgetRedirector import WidgetRedirector
 import traceback
 
+try:
+    from PIL import Image
+except:
+    Image = None
+try:
+    from PIL import ImageTk
+except:
+    ImageTk = None
+
 import petka
 
 APPNAME = "P1&2 Explorer"
@@ -73,8 +82,6 @@ class App(tkinter.Frame):
         self.curr_gui = []
         self.curr_lb_acts = None
         # canvas
-        self.curr_width = 0
-        self.curr_height = 0
         self.need_update = False
         self.canv_view_fact = 1
         self.main_image = tkinter.PhotoImage(width = 1, height = 1)
@@ -219,54 +226,81 @@ class App(tkinter.Frame):
         c = self.canv_view
         c.delete(tkinter.ALL)
         if self.sim is None: return
-        # Preview image        
-        self.canv_image = self.main_image.copy()
+
         w = self.canv_view.winfo_width() 
         h = self.canv_view.winfo_height()
         if (w == 0) or (h == 0): 
             return
         
-        scale = 0 #self.RadioGroupScale.get()
-        if scale == 0: # Fit
-            try:
-                psc = w / h
-                isc = self.curr_width / self.curr_height
-                if psc < isc:
-                    if w > self.curr_width:
-                        fact = w // self.curr_width
-                    else:
-                        fact = -self.curr_width // w
-                else:
-                    if h > self.curr_height:
-                        fact = h // self.curr_height
+        scale = 0
+
+        # Preview image
+        if not isinstance(self.main_image, tkinter.PhotoImage):
+            mw, mh = self.main_image.size
+            if scale == 0: # Fit
+                try:
+                    psc = w / h
+                    isc = mw / mh
+                    if psc < isc:
+                        fact = w / mw
                     else:
-                        fact = -self.curr_height // h
-            except:
-                fact = 1
+                        fact = h / mh
+                except:
+                    fact = 1.0
+            else:
+                fact = scale
+            pw = int(mw * fact)
+            ph = int(mh * fact)
+            img = self.main_image.resize((pw, ph))
+            self.canv_image = ImageTk.PhotoImage(img)
         else:
-            fact = scale
+            mw = self.main_image.width()
+            mh = self.main_image.height()
+            if scale == 0: # Fit
+                try:
+                    psc = w / h
+                    isc = mw / mh
+                    if psc < isc:
+                        if w > mw:
+                            fact = w // mw
+                        else:
+                            fact = -mw // w
+                    else:
+                        if h > mh:
+                            fact = h // mh
+                        else:
+                            fact = -mh // h
+                except:
+                    fact = 1
+            else:
+                fact = scale
+            self.canv_image = self.main_image.copy()
+            if fact > 0:
+                self.canv_image = self.canv_image.zoom(fact)
+            else:
+                self.canv_image = self.canv_image.subsample(-fact)
+            self.canv_image_fact = fact
 
-        # place on canvas
-        if fact > 0:
-            pw = self.curr_width * fact
-            ph = self.curr_height * fact
-        else:
-            pw = self.curr_width // -fact
-            ph = self.curr_height // -fact
+            # place on canvas
+            if fact > 0:
+                pw = mw * fact
+                ph = mh * fact
+            else:
+                pw = mw // -fact
+                ph = mh // -fact
 
         cw = max(pw, w)
         ch = max(ph, h)
         c.config(scrollregion = (0, 0, cw - 2, ch - 2))
-    
-        if fact > 0:
-            self.canv_image = self.canv_image.zoom(fact)
-        else:
-            self.canv_image = self.canv_image.subsample(-fact)
-        self.canv_image_fact = fact
         #print("Place c %d %d, p %d %d" % (cw, ch, w, h))
         c.create_image(cw // 2, ch // 2, image = self.canv_image)
        
-    def make_image(self, width, height, data):
+    def make_image(self, imgobj):
+        if imgobj.image is not None:
+            return imgobj.image
+        width = imgobj.width
+        height = imgobj.height
+        data = imgobj.rgb
         # create P6
         phdr = ("P6\n{} {}\n255\n".format(width, height))
         rawlen = width * height * 3 # RGB
@@ -538,9 +572,7 @@ class App(tkinter.Frame):
                 bmp = petka.BMPLoader()
                 bmp.load_data(bmpf)
                 self.main_image = \
-                    self.make_image(bmp.width, bmp.height, bmp.rgb)
-                self.curr_width = bmp.width
-                self.curr_height = bmp.height
+                    self.make_image(bmp)
                 self.switch_view(1)
                 self.update_canvas()
             except:
@@ -785,10 +817,7 @@ class App(tkinter.Frame):
             self.insert_lb_act("{} #{}".format(path[1], i), path[:2] + (i,))
         if path[1] == "image":
             self.switch_view(1)
-            self.main_image = tkinter.PhotoImage(\
-                file = "img/splash.gif")
-            self.curr_width = self.main_image.width()
-            self.curr_height = self.main_image.height()
+            self.main_image = tkinter.PhotoImage(file = "img/splash.gif")
         else:
             self.switch_view(0)
             self.clear_text()
diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
index da3bd3b8e..243779bb9 100644
--- a/engines/petka/petka/imgbmp.py
+++ b/engines/petka/petka/imgbmp.py
@@ -86,7 +86,7 @@ class BMPLoader:
         
     def load_data(self, f):
         try:
-            img = Image.open(f)
+            self.image = Image.open(f)
         except:
             f.seek(0)
             self.load_data_int(f)


Commit: bfabee3932ea4ea04a9c6c41cb364ff036a2488c
    https://github.com/scummvm/scummvm-tools/commit/bfabee3932ea4ea04a9c6c41cb364ff036a2488c
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Pillow support (not finished)

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/imgbmp.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index a365e0a4d..9532692ce 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -11,11 +11,11 @@ import traceback
 
 try:
     from PIL import Image
-except:
+except ImportError:
     Image = None
 try:
     from PIL import ImageTk
-except:
+except ImportError:
     ImageTk = None
 
 import petka
diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
index 243779bb9..3016593fa 100644
--- a/engines/petka/petka/imgbmp.py
+++ b/engines/petka/petka/imgbmp.py
@@ -8,7 +8,7 @@ from . import EngineError
 
 try:
     from PIL import Image
-except:
+except ImportError:
     Image = None
 
 class BMPLoader:
@@ -17,10 +17,8 @@ class BMPLoader:
         self.image = None
         self.width = 0
         self.height = 0
-        self.imginfo = ""
         
-    def load_data_int(self, f):
-        # TODO: normal BMP, rle BMP
+    def load_data_int16(self, f):
         # check magic string "BM"
         temp = f.read(2)
         if temp != b"BM":
@@ -38,9 +36,7 @@ class BMPLoader:
         if pict[0] != 40:
             raise EngineError("Unsupported InfoHeader")
         pictw = pict[1]
-        self.width = pictw
         picth = pict[2]
-        self.height = picth
         
         # read data_offset - 40 - 6 bytes
         delta = data_offset - 40 - 6
@@ -68,26 +64,38 @@ class BMPLoader:
         if len(f.read()) > 0:
             raise EngineError("BMP read error, some data unparsed")
                 
+        return pictw, picth, picture_data
+        
+    def pixelswap16(self, pw, ph, pd):
         # convert 16 bit to 24
         b16arr = array.array("H") # unsigned short
-        b16arr.frombytes(picture_data)
+        b16arr.frombytes(pd)
         rgb = array.array("B")
         for b16 in b16arr:
             rgb.append((b16 >> 5) & 0b11111000)
             rgb.append((b16 << 5) & 0b11100000 | (b16 >> 11) & 0b00011100)
             rgb.append((b16 << 0) &0b11111000) 
         # Y-mirror
-        self.rgb = array.array("B")
-        for i in range(picth):
-            off = (picth - i - 1) * pictw * 3
-            self.rgb += rgb[off:off + pictw * 3]
+        newrgb = array.array("B")
+        for i in range(ph):
+            off = (ph - i - 1) * pw * 3
+            newrgb += rgb[off:off + pw * 3]
+        return newrgb
         
-        self.imginfo = "Internal BMP loader"
         
     def load_data(self, f):
         try:
-            self.image = Image.open(f)
+            pw, ph, pd = self.load_data_int16(f)
+            if Image:
+                
+                self.image = Image.frombytes("RGB;16", (pw, ph), pd, "bit", 16, 0, 0)
+            else:
+                self.width = pw
+                self.height = ph
+                self.rgb = self.pixelswap16(pw, ph, pd)
         except:
+            import traceback
+            traceback.print_exc()
             f.seek(0)
-            self.load_data_int(f)
-        
+            self.image = Image.open(f)
+            


Commit: b9c84877f91fbe19796e28f76fd317a06d693247
    https://github.com/scummvm/scummvm-tools/commit/b9c84877f91fbe19796e28f76fd317a06d693247
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Reading BMP with PILLOW (baggy)

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/imgbmp.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 9532692ce..dfbd1f5b1 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -139,7 +139,8 @@ class App(tkinter.Frame):
         self.path_handler["test"] = self.path_test
         
         self.update_after()
-        self.open_path(self.find_path_scene(36))
+        #self.open_path(self.find_path_scene(36))
+        self.open_path(["res", "flt", "BMP", 7])
 
     def create_menu(self):
         self.menubar = tkinter.Menu(self.master)
@@ -533,7 +534,7 @@ class App(tkinter.Frame):
         elif path[1] == "all":
             return self.path_res_all(path)
         else:
-            return self.path_default()
+            return self.path_default(path)
             
             
         if self.last_path[:2] != path[:2]:
diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
index 3016593fa..713c33f0b 100644
--- a/engines/petka/petka/imgbmp.py
+++ b/engines/petka/petka/imgbmp.py
@@ -2,7 +2,7 @@
 
 # romiq.kh at gmail.com, 2014
 
-import array, struct
+import array, struct, io
 
 from . import EngineError
 
@@ -50,6 +50,7 @@ class BMPLoader:
 
         bsz = pictw * picth * 2
         picture_data = f.read(bsz)
+ 
         off += bsz
         if len(picture_data) != bsz:
             raise EngineError("Bitmap truncated, need {}, got {}".format(bsz, \
@@ -87,8 +88,20 @@ class BMPLoader:
         try:
             pw, ph, pd = self.load_data_int16(f)
             if Image:
-                
-                self.image = Image.frombytes("RGB;16", (pw, ph), pd, "bit", 16, 0, 0)
+                # reload fixed
+                f.seek(0)
+                d = io.BytesIO()
+                d.write(f.read(10))
+                data_off = struct.unpack("<I", f.read(4))[0]
+                d.write(struct.pack("<I", data_off - 1))
+                d.write(f.read(data_off - 14))
+                #fmt = "{}H".format(pw * ph)
+                #data = struct.unpack(">" + fmt, f.read(pw * ph * 2))
+                #d.write(struct.pack("<" + fmt, *data))
+                d.write(f.read(pw * ph * 2))
+                d.write(b"\xFF" * 10)
+                d.seek(0)
+                self.image = Image.open(d)
             else:
                 self.width = pw
                 self.height = ph


Commit: 2603ab1f9b7b419c1a1f7797f10cb977d310e633
    https://github.com/scummvm/scummvm-tools/commit/2603ab1f9b7b419c1a1f7797f10cb977d310e633
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Reading BMP with PILLOW (baggy)

Changed paths:
    engines/petka/petka/imgbmp.py


diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
index 713c33f0b..90e72a8b2 100644
--- a/engines/petka/petka/imgbmp.py
+++ b/engines/petka/petka/imgbmp.py
@@ -89,19 +89,20 @@ class BMPLoader:
             pw, ph, pd = self.load_data_int16(f)
             if Image:
                 # reload fixed
-                f.seek(0)
-                d = io.BytesIO()
-                d.write(f.read(10))
-                data_off = struct.unpack("<I", f.read(4))[0]
-                d.write(struct.pack("<I", data_off - 1))
-                d.write(f.read(data_off - 14))
+                #f.seek(0)
+                #d = io.BytesIO()
+                #d.write(f.read(10))
+                #data_off = struct.unpack("<I", f.read(4))[0]
+                #d.write(struct.pack("<I", data_off - 1))
+                #d.write(f.read(data_off - 14))
                 #fmt = "{}H".format(pw * ph)
                 #data = struct.unpack(">" + fmt, f.read(pw * ph * 2))
                 #d.write(struct.pack("<" + fmt, *data))
-                d.write(f.read(pw * ph * 2))
-                d.write(b"\xFF" * 10)
-                d.seek(0)
-                self.image = Image.open(d)
+                #d.write(f.read(pw * ph * 2))
+                #d.write(b"\xFF" * 10)
+                #d.seek(0)
+                #self.image = Image.open(d)
+                self.image = Image.frombytes("RGB", (pw, ph), pd, "BGR;16") 
             else:
                 self.width = pw
                 self.height = ph


Commit: cf98f30a46b10781f38a8f04b791c4ee6040f12c
    https://github.com/scummvm/scummvm-tools/commit/cf98f30a46b10781f38a8f04b791c4ee6040f12c
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: BMP can be loaded correctly most times

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/imgbmp.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index dfbd1f5b1..d0623303e 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -139,8 +139,8 @@ class App(tkinter.Frame):
         self.path_handler["test"] = self.path_test
         
         self.update_after()
-        #self.open_path(self.find_path_scene(36))
-        self.open_path(["res", "flt", "BMP", 7])
+        self.open_path(self.find_path_scene(36))
+        #self.open_path(["res", "flt", "BMP", 7])
 
     def create_menu(self):
         self.menubar = tkinter.Menu(self.master)
@@ -451,6 +451,15 @@ class App(tkinter.Frame):
             if rec.idx == scn_idx:
                 return ["scenes", idx]
         return ["no_scene", scn_idx]
+
+    def find_path_obj_scene(self, rec_idx):
+        for idx, rec in enumerate(self.sim.objects):
+            if rec.idx == rec_idx:
+                return ["objs", idx]
+        for idx, rec in enumerate(self.sim.scenes):
+            if rec.idx == rec_idx:
+                return ["scenes", idx]
+        return ["no_obj_scene", rec_idx]
         
     def find_path_name(self, key):
         for idx, name in enumerate(self.sim.namesord):
@@ -670,8 +679,8 @@ class App(tkinter.Frame):
         else:
             # record info
             self.insert_text(("Object" if isobj else "Scene") + ":\n")
-            self.insert_text("  Index: {}\n  Name:  {}\n".\
-                format(rec.idx, rec.name))
+            self.insert_text("  Index: {} (0x{:X})\n  Name:  {}\n".\
+                format(rec.idx, rec.idx, rec.name))
             if rec.name in self.sim.names:
                 self.insert_text("  ")
                 self.insert_text("Alias", self.find_path_name(rec.name))
@@ -728,7 +737,7 @@ class App(tkinter.Frame):
                         self.insert_text("THIS")
                     else:
                         self.insert_text("{}".format(op[0]), \
-                            self.find_path_obj(op[0]))
+                            self.find_path_obj_scene(op[0]))
                     msg = ""
                     if op[2] != 0xffff:
                         if op[2] not in resused and op[2] in self.sim.res:
@@ -749,8 +758,8 @@ class App(tkinter.Frame):
                     self.insert_text("  ")
                     self.insert_text("{}".format(res_id), \
                         self.find_path_res(res_id))
-                    self.insert_text(" - {} (0x{:X})\n".\
-                        format(self.sim.res[res_id], res_id))
+                    self.insert_text(" (0x{:X}) - {}\n".\
+                        format(res_id, self.sim.res[res_id]))
                 
             
 
diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
index 90e72a8b2..41565ebc3 100644
--- a/engines/petka/petka/imgbmp.py
+++ b/engines/petka/petka/imgbmp.py
@@ -57,13 +57,15 @@ class BMPLoader:
                 len(picture_data)))
 
         # read 2 zero bytes
-        temp = f.read(2)
-        if temp != b"\x00\x00":
-            raise EngineError("Magic zero bytes absent or mismatch")
-        off += 2
+        #temp = f.read(2)
+        #if temp != b"\x00\x00":
+        #    raise EngineError("Magic zero bytes absent or mismatch")
+        #off += 2
 
-        if len(f.read()) > 0:
-            raise EngineError("BMP read error, some data unparsed")
+        temp = f.read(2)
+        if len(temp) > 0:
+            if temp != b"\x00\x00":
+                raise EngineError("BMP read error, some data unparsed")
                 
         return pictw, picth, picture_data
         
@@ -71,17 +73,32 @@ class BMPLoader:
         # convert 16 bit to 24
         b16arr = array.array("H") # unsigned short
         b16arr.frombytes(pd)
-        rgb = array.array("B")
-        for b16 in b16arr:
-            rgb.append((b16 >> 5) & 0b11111000)
-            rgb.append((b16 << 5) & 0b11100000 | (b16 >> 11) & 0b00011100)
-            rgb.append((b16 << 0) &0b11111000) 
-        # Y-mirror
-        newrgb = array.array("B")
-        for i in range(ph):
-            off = (ph - i - 1) * pw * 3
-            newrgb += rgb[off:off + pw * 3]
-        return newrgb
+        b16arr.byteswap()
+        
+        #rgb = array.array("B")
+        #for b16 in b16arr:
+            #rgb.append((b16 >> 5) & 0b11111000)
+            #rgb.append((b16 << 5) & 0b11100000 | (b16 >> 11) & 0b00011100)
+            #rgb.append((b16 << 0) &0b11111000) 
+        #    rgb.append((b16 << 3) & 0b11111000)
+        #    rgb.append((b16 >> 3) & 0b11111100)
+        #    rgb.append((b16 >> 8) & 0b11111000)
+        ## Y-mirror
+        #newrgb = array.array("B")
+        #for i in range(ph):
+        #    off = (ph - i - 1) * pw * 3
+        #    newrgb += rgb[off:off + pw * 3]
+        #return newrgb
+        
+        rgb = array.array("B", [0] * pw * ph * 3)
+        for j in range(ph):
+            for i in range(pw):
+                off = (ph - j - 1) * pw * 3 + i * 3
+                b16 = b16arr[j * pw + i]
+                rgb[off] = (b16 << 3) & 0b11111000
+                rgb[off + 1] = (b16 >> 3) & 0b11111100
+                rgb[off + 2] = (b16 >> 8) & 0b11111000
+        return rgb
         
         
     def load_data(self, f):
@@ -102,7 +119,8 @@ class BMPLoader:
                 #d.write(b"\xFF" * 10)
                 #d.seek(0)
                 #self.image = Image.open(d)
-                self.image = Image.frombytes("RGB", (pw, ph), pd, "BGR;16") 
+                pd = self.pixelswap16(pw, ph, pd).tobytes()
+                self.image = Image.frombytes("RGB", (pw, ph), pd) 
             else:
                 self.width = pw
                 self.height = ph


Commit: 8afb6d50b00a0cc4972f2714245cc8b8b2ca8c8f
    https://github.com/scummvm/scummvm-tools/commit/8afb6d50b00a0cc4972f2714245cc8b8b2ca8c8f
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Select only one item in listbox, refine

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index d0623303e..3a52433e6 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -91,7 +91,8 @@ class App(tkinter.Frame):
         
         ttk.Style().configure("Tool.TButton", width = -1) # minimal width
         ttk.Style().configure("TLabel", padding = self.pad)
-        ttk.Style().configure('Info.TFrame', background = 'white', foreground = "black")
+        ttk.Style().configure('Info.TFrame', background = 'white', \
+            foreground = "black")
         
         # main paned
         self.pan_main = ttk.PanedWindow(self, orient = tkinter.HORIZONTAL)
@@ -411,15 +412,17 @@ class App(tkinter.Frame):
         self.curr_lb.insert(tkinter.END, name)
 
     def select_lb_item(self, idx):
-        try:
-            num = self.curr_lb.curselection()[0]
-            num = int(num)
-        except:
-            num = -1
-        if idx != num:
+        idx = "{}".format(idx)
+        need = True
+        for sel in self.curr_lb.curselection():
+            if sel == idx:
+                need = False
+            else:
+                self.curr_lb.selection_clear(sel)
+        if need:
             self.curr_lb.selection_set(idx)
-            self.curr_lb.see(idx)
-
+        self.curr_lb.see(idx)
+            
     def on_left_listbox(self, event):
         def currsel():
             try:
@@ -693,7 +696,7 @@ class App(tkinter.Frame):
             # references / backreferences                    
             if isobj:
                 # search where object used
-                self.insert_text("\nUsed in:\n")
+                self.insert_text("\nRefered by scenes:\n")
                 for scn in self.sim.scenes:
                     for ref in scn.refs:
                         if ref[0].idx == rec.idx:


Commit: e98266437bcc5536484439070173a8d0b7359f8d
    https://github.com/scummvm/scummvm-tools/commit/e98266437bcc5536484439070173a8d0b7359f8d
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: REfactor: internal locations and text tagging

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 3a52433e6..ce0259371 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -22,6 +22,9 @@ import petka
 
 APPNAME = "P1&2 Explorer"
 
+def hlesc(value):
+    return value.replace("\\", "\\\\").replace("<", "\\<").replace(">", "\\>")
+
 # thanx to http://effbot.org/zone/tkinter-text-hyperlink.htm
 class HyperlinkManager:
     def __init__(self, text):
@@ -30,6 +33,15 @@ class HyperlinkManager:
         self.text.tag_bind("hyper", "<Enter>", self._enter)
         self.text.tag_bind("hyper", "<Leave>", self._leave)
         self.text.tag_bind("hyper", "<Button-1>", self._click)
+        bold_font = font.Font(text, self.text.cget("font"))
+        bold_font.configure(weight = "bold")
+        self.text.tag_config("bold", font = bold_font)
+        italic_font = font.Font(text, self.text.cget("font"))
+        italic_font.configure(slant = "italic")
+        self.text.tag_config("italic", font = italic_font)
+        underline_font = font.Font(text, self.text.cget("font"))
+        underline_font.configure(underline = 1)
+        self.text.tag_config("underline", font = underline_font)
         self.reset()
 
     def reset(self):
@@ -140,7 +152,8 @@ class App(tkinter.Frame):
         self.path_handler["test"] = self.path_test
         
         self.update_after()
-        self.open_path(self.find_path_scene(36))
+        self.open_path("")
+        #self.open_path(self.find_path_scene(36))
         #self.open_path(["res", "flt", "BMP", 7])
 
     def create_menu(self):
@@ -212,7 +225,19 @@ class App(tkinter.Frame):
     def on_resize_view(self, event):
         self.update_after()
  
-    def open_path(self, path):
+    def open_path(self, loc):
+        if isinstance(loc, str):
+            path = []
+            if loc[:1] == "/":
+                loc = loc[1:]
+            if loc != "":
+                for item in loc.split("/"):
+                    try:
+                        path.append(int(item, 10))
+                    except:
+                        path.append(item)
+        else:
+            path = loc
         path = tuple(path)
         print("DEBUG: Open", path)
         self.curr_path = path
@@ -253,7 +278,7 @@ class App(tkinter.Frame):
                 fact = scale
             pw = int(mw * fact)
             ph = int(mh * fact)
-            img = self.main_image.resize((pw, ph))
+            img = self.main_image.resize((pw, ph), Image.ANTIALIAS)
             self.canv_image = ImageTk.PhotoImage(img)
         else:
             mw = self.main_image.width()
@@ -390,23 +415,60 @@ class App(tkinter.Frame):
             self.scr_view_x.config(command = self.canv_view.xview)
             self.scr_view_y.config(command = self.canv_view.yview)
 
-    def clear_text(self):
+    def clear_info(self):
         self.text_view.delete(0.0, tkinter.END)
 
-    def insert_text(self, text, link = None):
-        if link:
-            if callable(link):
-                cb = link
-            else: 
-                def make_cb(path):
-                    def cb():
-                        return self.open_path(path)
-                    return cb
-                cb = make_cb(tuple(link))
-            self.text_view.insert(tkinter.INSERT, text, self.text_hl.add(cb))
-        else:
-            self.text_view.insert(tkinter.INSERT, text)
-
+    def add_info(self, text):
+        mode = 0 # 0 - normal, 1 - tag
+        curr_tag = None
+        curr_text = ""
+        tags = []
+        esc = False
+        for ch in text:
+            if mode == 0:
+                if esc:
+                    curr_text += ch
+                    esc = False
+                else:
+                    if ch == "\\":
+                        esc = True
+                    elif ch == "<":
+                        mode = 1
+                        curr_tag = ""
+                    else:
+                        curr_text += ch
+            else:
+                if ch == ">":
+                    if len(curr_text) > 0:                    
+                        self.text_view.insert(tkinter.INSERT, curr_text, \
+                            tuple([x for x in tags for x in x]))
+                    if curr_tag[:7] == "a href=":
+                        ref = curr_tag[7:]
+                        if ref[:1] == "\"":
+                            ref = ref[1:]
+                        if ref[-1:] == "\"":
+                            ref = ref[:-1]
+                        def make_cb(path):
+                            def cb():
+                                return self.open_path(path)
+                            return cb
+                        tags.append(self.text_hl.add(make_cb(ref)))
+                    elif curr_tag == "b":
+                        tags.append(["bold"])
+                    elif curr_tag == "i":
+                        tags.append(["italic"])
+                    elif curr_tag == "u":
+                        tags.append(["underline"])
+                    elif curr_tag[:1] == "/":
+                        tags = tags[:-1]
+                    curr_text = ""
+                    mode = 0
+                else:
+                    curr_tag += ch
+        if len(curr_text) > 0:                    
+            self.text_view.insert(tkinter.INSERT, curr_text, \
+                tuple([x for x in tags for x in x]))
+        
     def insert_lb_act(self, name, act):
         self.curr_lb_acts.append((name, act))
         self.curr_lb.insert(tkinter.END, name)
@@ -440,52 +502,68 @@ class App(tkinter.Frame):
     def find_path_res(self, res):
         for idx, res_id in enumerate(self.sim.resord):
             if res_id == res:
-                return ["res", "all", idx]
-        return ["no_res", res]
+                return "/res/all/{}".format(idx)
+        return "/no_res/{}".format(res)
 
     def find_path_obj(self, obj_idx):
         for idx, rec in enumerate(self.sim.objects):
             if rec.idx == obj_idx:
-                return ["objs", idx]
-        return ["no_obj", obj_idx]
+                return "/objs/{}".format(idx)
+        return "/no_obj/{}".format(obj_idx)
 
     def find_path_scene(self, scn_idx):
         for idx, rec in enumerate(self.sim.scenes):
             if rec.idx == scn_idx:
-                return ["scenes", idx]
-        return ["no_scene", scn_idx]
+                return "/scenes/{}".format(idx)
+        return "/no_scene/{}".format(scn_idx)
 
     def find_path_obj_scene(self, rec_idx):
         for idx, rec in enumerate(self.sim.objects):
             if rec.idx == rec_idx:
-                return ["objs", idx]
+                return "/objs/{}".format(idx)
         for idx, rec in enumerate(self.sim.scenes):
             if rec.idx == rec_idx:
-                return ["scenes", idx]
-        return ["no_obj_scene", rec_idx]
+                return "/scenes/{}".format(idx)
+        return "/no_obj_scene/{}".format(rec_idx)
         
     def find_path_name(self, key):
         for idx, name in enumerate(self.sim.namesord):
             if name == key:
-                return ["names", idx]
-        return ["no_name", key]
+                return "/names/{}".format(idx)
+        return "/no_name/{}".format(key)
 
     def find_path_invntr(self, key):
         for idx, name in enumerate(self.sim.invntrord):
             if name == key:
-                return ["invntr", idx]
-        return ["no_invntr", key]
+                return "/invntr/{}".format(idx)
+        return "/no_invntr/{}".format(key)
+
+    def path_info_outline(self):
+        self.add_info("Current part {} chapter {}\n\n".\
+                format(self.sim.curr_part, self.sim.curr_chap))
+        self.add_info("  Resources: <a href=\"/res\">{}</a>\n".\
+            format(len(self.sim.res)))
+        self.add_info("  Objects:   <a href=\"/objs\">{}</a>\n".\
+            format(len(self.sim.objects)))
+        self.add_info("  Scenes:    <a href=\"/scenes\">{}</a>\n".\
+            format(len(self.sim.scenes)))
+        self.add_info("  Names:     <a href=\"/names\">{}</a>\n".\
+            format(len(self.sim.names)))
+        self.add_info("  Invntr:    <a href=\"/invntr\">{}</a>\n".\
+            format(len(self.sim.invntr)))
+    
 
     def path_default(self, path):
         self.switch_view(0)
         self.update_gui("Outline")
-        self.clear_text()
+        self.clear_info()
         if len(path) != 0:
             spath = ""
             for item in path:
                 spath += "/" + str(item)
-            self.insert_text("Path {} not found\n\n".format(spath))
-        self.insert_text("Select from outline\n")
+            self.add_info("Path {} not found\n\n".format(spath))
+        self.add_info("Select from <b>outline</b>\n\n")
+        self.path_info_outline()
         if self.sim is not None:
             acts = [
                 ("Parts ({})".format(len(self.sim.parts)), ["parts"]),
@@ -522,18 +600,9 @@ class App(tkinter.Frame):
                 cnum = 0
             self.sim.open_part(pnum, cnum)
         # display
-        self.clear_text()
-        self.insert_text("Current: part {} chapter {}\n\n  Resources: ".\
-                format(self.sim.curr_part, self.sim.curr_chap))
-        self.insert_text("{}".format(len(self.sim.res)), ["res"])
-        self.insert_text("\n  Objects:   ")
-        self.insert_text("{}".format(len(self.sim.objects)), ["objs"])
-        self.insert_text("\n  Scenes:    ")
-        self.insert_text("{}".format(len(self.sim.scenes)), ["scenes"])
-        self.insert_text("\n  Names:     ")
-        self.insert_text("{}".format(len(self.sim.names)), ["names"])
-        self.insert_text("\n  Invntr:    ")
-        self.insert_text("{}".format(len(self.sim.invntr)), ["invntr"])
+        self.clear_info()
+        self.add_info("Select <b>part</b>\n\n")
+        self.path_info_outline()
 
     def path_res(self, path):
         # res - full list
@@ -547,8 +616,7 @@ class App(tkinter.Frame):
             return self.path_res_all(path)
         else:
             return self.path_default(path)
-            
-            
+                        
         if self.last_path[:2] != path[:2]:
             # 
             self.switch_view(0)
@@ -561,7 +629,6 @@ class App(tkinter.Frame):
             for idx, name in enumerate(self.sim.invntrord):
                 self.insert_lb_act(name, ["invntr", idx])
 
-        pass
         # list resources
         if self.curr_mode_sub is None:
             lb = self.update_gui_add_left_listbox("Resources") 
@@ -590,23 +657,22 @@ class App(tkinter.Frame):
                 self.update_canvas()
             except:
                 self.switch_view(0)
-                self.clear_text()
-                self.insert_text("Error loading {} - \"{}\" \n\n{}".\
+                self.clear_info()
+                self.add_info("Error loading {} - \"{}\" \n\n{}".\
                     format(res_id, fn, traceback.format_exc()))
             finally:
                 bmpf.close()
         else:
             self.switch_view(0)
-            self.clear_text()
-            self.insert_text("Resource {} - \"{}\" cannot be displayed\n".\
+            self.clear_info()
+            self.add_info("Resource {} - \"{}\" cannot be displayed\n".\
                 format(res_id, fn))
 
     def path_res_status(self):
         self.switch_view(0)
-        self.clear_text()
-        self.insert_text("Total: ")
-        self.insert_text("{}".format(len(self.sim.res)), ["res"])
-        self.insert_text("\nFiletypes:\n")
+        self.clear_info()
+        self.add_info("<b>Resources</b>: <a href=\"/res\">{}</a>\nFiletypes:\n".format(\
+            len(self.sim.res)))
         fts = {}
         for res in self.sim.res.values():
             fp = res.rfind(".")
@@ -616,9 +682,8 @@ class App(tkinter.Frame):
         ftk = list(fts.keys())
         ftk.sort()
         for ft in ftk:
-            self.insert_text("  ")
-            self.insert_text(ft, ["res", "flt", ft])
-            self.insert_text(": {}\n".format(fts[ft]))
+            self.add_info("  <a href=\"/res/flt/{}\">{}</a>: {}\n".format(\
+                ft, ft, fts[ft]))
 
     def path_res_all(self, path):
         if self.last_path[:2] != ("res", "all",):
@@ -676,44 +741,44 @@ class App(tkinter.Frame):
             self.select_lb_item(path[1])
             rec = lst[path[1]]
         # display
-        self.clear_text()
+        self.clear_info()
         if not rec:
-            self.insert_text("Select item from list\n")
+            self.add_info("Select item from list\n")
         else:
             # record info
-            self.insert_text(("Object" if isobj else "Scene") + ":\n")
-            self.insert_text("  Index: {} (0x{:X})\n  Name:  {}\n".\
-                format(rec.idx, rec.idx, rec.name))
+            self.add_info(("<b>Object</b>" if isobj \
+                else "<b>Scene</b>") + ":\n")
+            self.add_info("  Index:  {} (0x{:X})\n  Name:   {}\n".\
+                format(rec.idx, rec.idx, hlesc(rec.name)))
             if rec.name in self.sim.names:
-                self.insert_text("  ")
-                self.insert_text("Alias", self.find_path_name(rec.name))
-                self.insert_text(":  {}\n".format(self.sim.names[rec.name]))
+                self.add_info("  <a href=\"{}\">Alias</a>:  {}\n".format(\
+                    self.find_path_name(rec.name), \
+                    hlesc(self.sim.names[rec.name])))
             if rec.name in self.sim.invntr:
-                self.insert_text("  ")
-                self.insert_text("Invntr", self.find_path_invntr(rec.name))
-                self.insert_text(": {}\n".format(self.sim.invntr[rec.name]))
+                self.add_info("  <a href=\"{}\">Invntr</a>: {}\n".format(\
+                    self.find_path_invntr(rec.name), \
+                    hlesc(self.sim.invntr[rec.name])))
 
             # references / backreferences                    
             if isobj:
                 # search where object used
-                self.insert_text("\nRefered by scenes:\n")
+                self.add_info("\n<b>Refered by scenes</b>:\n")
                 for scn in self.sim.scenes:
                     for ref in scn.refs:
                         if ref[0].idx == rec.idx:
-                            self.insert_text("  ")
-                            self.insert_text("{}".format(scn.idx), \
-                                self.find_path_scene(scn.idx))
-                            self.insert_text(" - {}\n".format(scn.name))
+                            self.add_info("  <a href=\"{}\">{}</a> (0x{:X}) "\
+                                "- {}\n".format(self.find_path_scene(scn.idx), \
+                                scn.idx, scn.idx, scn.name))
                             break
             else:
                 if len(rec.refs) == 0:
-                    self.insert_text("\nNo references\n")
+                    self.add_info("\nNo references\n")
                 else:
-                    self.insert_text("\nReferences: {}\n".format(len(rec.refs)))
+                    self.add_info("\n<b>References</b>: {}\n".\
+                        format(len(rec.refs)))
                 for idx, ref in enumerate(rec.refs):
-                    self.insert_text("  {}) ".format(idx))
-                    self.insert_text("{}".format(ref[0].idx), \
-                        self.find_path_obj(ref[0].idx))
+                    self.add_info("  {}) <a href=\"{}\">{}</a>".format(idx,\
+                        self.find_path_obj(ref[0].idx), ref[0].idx))
                     msg = ""
                     for arg in ref[1:]:
                         msg += " "
@@ -723,24 +788,24 @@ class App(tkinter.Frame):
                             msg += "-1"
                         else:
                             msg += "0x{:X}".format(arg)
-                    self.insert_text(msg + " / {}\n".format(ref[0].name))
+                    self.add_info(msg + " / {}\n".format(hlesc(ref[0].name)))
 
             resused = []
-            self.insert_text("\nHandlers: {}\n".format(len(rec.acts)))
+            self.add_info("\n<b>Handlers</b>: {}\n".format(len(rec.acts)))
             for idx, (act_id, act_cond, act_arg, ops) in enumerate(rec.acts):
                 msg = petka.OPCODES.get(act_id, ["OP_{:X}".format(act_id)])[0]
                 if act_cond != 0xff or act_arg != 0xffff:
                     msg += " 0x{:02X} 0x{:04X}".format(act_cond, act_arg)
-                self.insert_text("  {}) on {}, ops: {}\n".format(\
+                self.add_info("  {}) <u>on {}</u>, ops: {}\n".format(\
                     idx, msg, len(ops)))
                 for oidx, op in enumerate(ops):
                     msg = petka.OPCODES.get(op[1], ["OP_{:X}".format(op[1])])[0]
-                    self.insert_text("    {}) {} ".format(oidx, msg))
+                    self.add_info("    {}) {} ".format(oidx, msg))
                     if op[0] == rec.idx:
-                        self.insert_text("THIS")
+                        self.add_info("THIS")
                     else:
-                        self.insert_text("{}".format(op[0]), \
-                            self.find_path_obj_scene(op[0]))
+                        self.add_info("<a href=\"{}\">{}</a>".format(\
+                            self.find_path_obj_scene(op[0]), op[0]))
                     msg = ""
                     if op[2] != 0xffff:
                         if op[2] not in resused and op[2] in self.sim.res:
@@ -753,17 +818,15 @@ class App(tkinter.Frame):
                             msg += "-1"
                         else:
                             msg += "0x{:X}".format(arg)
-                    self.insert_text("{}\n".format(msg))
+                    self.add_info("{}\n".format(msg))
                     
             if len(resused) > 0:
-                self.insert_text("\nUsed resources: {}\n".format(len(resused)))
+                self.add_info("\n<b>Used resources</b>: {}\n".\
+                    format(len(resused)))
                 for res_id in resused:
-                    self.insert_text("  ")
-                    self.insert_text("{}".format(res_id), \
-                        self.find_path_res(res_id))
-                    self.insert_text(" (0x{:X}) - {}\n".\
-                        format(res_id, self.sim.res[res_id]))
-                
+                    self.add_info("  <a href=\"{}\">{}</a> (0x{:X}) - {}\n".\
+                        format(self.find_path_res(res_id), res_id, res_id, \
+                        hlesc(self.sim.res[res_id])))
             
 
     def path_names(self, path):
@@ -779,20 +842,20 @@ class App(tkinter.Frame):
             self.select_lb_item(path[1])
             name = self.sim.namesord[path[1]]
         # display
-        self.clear_text()
+        self.clear_info()
         if not name:
-            self.insert_text("Select name from list\n")
+            self.add_info("Select <b>name</b>\n")
         else:
             # name info
-            self.insert_text("Alias: {}\n".format(name))
-            self.insert_text("Value: {}\n\n".format(self.sim.names[name]))
+            self.add_info("<b>Alias</b>: {}\n".format(hlesc(name)))
+            self.add_info("Value: {}\n\n".format(self.sim.names[name]))
             # search for objects
-            self.insert_text("Applied for:\n")
+            self.add_info("<b>Applied for</b>:\n")
             for idx, obj in enumerate(self.sim.objects):
                 if obj.name == name:
-                    self.insert_text("  ")
-                    self.insert_text("{}".format(obj.idx), ["objs", idx])
-                    self.insert_text(" - {}\n".format(obj.name))
+                    self.add_info("  <a href=\"/objs/{}\">{}</a> (0x{:X}) "\
+                        "- {}\n".format(idx, obj.idx, obj.idx, \
+                        hlesc(obj.name)))
 
     def path_invntr(self, path):
         self.switch_view(0)
@@ -807,20 +870,20 @@ class App(tkinter.Frame):
             self.select_lb_item(path[1])
             name = self.sim.invntrord[path[1]]
         # display
-        self.clear_text()
+        self.clear_info()
         if not name:
-            self.insert_text("Select invntr from list\n")
+            self.add_info("Select <b>invntr</b>\n")
         else:
             # invntr info
-            self.insert_text("Invntr: {}\n".format(name))
-            self.insert_text("{}\n\n".format(self.sim.invntr[name]))
+            self.add_info("<b>Invntr</b>: {}\n".format(name))
+            self.add_info("{}\n\n".format(hlesc(self.sim.invntr[name])))
             # search for objects
-            self.insert_text("Applied for:\n")
+            self.add_info("<b>Applied for</b>:\n")
             for idx, obj in enumerate(self.sim.objects):
                 if obj.name == name:
-                    self.insert_text("  ")
-                    self.insert_text("{}".format(obj.idx), ["objs", idx])
-                    self.insert_text(" - {}\n".format(obj.name))
+                    self.add_info("  <a href=\"/objs/{}\">{}</a> (0x{:X}) "\
+                        "- {}\n".format(idx, obj.idx, obj.idx, \
+                        hlesc(obj.name)))
 
     def path_test(self, path):
         self.update_gui("Test {}".format(path[1]))
@@ -833,10 +896,10 @@ class App(tkinter.Frame):
             self.main_image = tkinter.PhotoImage(file = "img/splash.gif")
         else:
             self.switch_view(0)
-            self.clear_text()
-            self.insert_text("Information panel for {}\n".format(path))
+            self.clear_info()
+            self.add_info("Information panel for {}\n".format(path))
             for i in range(100):
-                self.insert_text("  Item {}\n".format(i))
+                self.add_info("  Item {}\n".format(i))
 
     def on_open_data(self):
         # open data - select TODO


Commit: 8c273ad39744cf27be1bac89fc14bd75d669e8bb
    https://github.com/scummvm/scummvm-tools/commit/8c273ad39744cf27be1bac89fc14bd75d669e8bb
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Information for bmp and same

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/imgbmp.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index ce0259371..18a1a4b1e 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -644,7 +644,71 @@ class App(tkinter.Frame):
                     lb.insert(tkinter.END, "{} - {}".format(res_id, \
                         self.sim.res[res_id]))
 
-    def path_res_open(self, res_id):
+    def path_res_open(self, res_id, mode):
+        if len(mode) == 0:
+            self.switch_view(0)
+            fn = self.sim.res[res_id]
+            self.clear_info()
+            self.add_info("<b>Resource</b>: {} (0x{:X}) - \"{}\"\n\n".\
+                format(res_id, res_id, hlesc(fn)))
+            resref = self.find_path_res(res_id)
+            self.add_info("<a href=\"{}/view\">View</a> "\
+                "<a href=\"{}/used\">Used by</a>\n\n".\
+                format(resref, resref))
+            try:
+                if fn[-4:].lower() == ".bmp":
+                    self.add_info("<b>BMP image</b>: ")
+                    bmpf = self.sim.fman.read_file_stream(fn)
+                    bmp = petka.BMPLoader()
+                    bmp.load_info(bmpf)
+                    if bmp.image:
+                        # PIL
+                        self.add_info("Python Imaging\n")
+                        self.add_info("  Mode: {}\n  Size: {}x{}".\
+                            format(bmp.image.mode, \
+                                bmp.image.size[0], bmp.image.size[1]))
+                    else:    
+                        self.add_info("internal BMP loader\n  "\
+                            "Mode: 16-bit\n  Size: {}x{}".\
+                            format(bmp.width, bmp.height))
+                if fn[-4:].lower() == ".flc":
+                    self.add_info("<b>FLC animation</b>: ")
+                else:
+                    self.add_info("No information availiable")
+            except:
+                self.add_info("Error loading {} - \"{}\" \n\n{}".\
+                    format(res_id, hlesc(fn), hlesc(traceback.format_exc())))
+                    
+        elif mode[0] == "view":
+            self.path_res_view(res_id)
+        elif mode[0] == "used":
+            self.switch_view(0)
+            fn = self.sim.res[res_id]
+            self.clear_info()
+            resref = self.find_path_res(res_id)
+            self.add_info("<b>Resource</b>: <a href=\"{}\">{}</a> (0x{:X}) "\
+                "- \"{}\"\n\n".format(resref, res_id, res_id, hlesc(fn)))
+            
+            def usedby(lst, tp):
+                for idx, rec in enumerate(lst):
+                    ru = False
+                    for act_id, act_cond, act_arg, ops in rec.acts:
+                        if ru: break
+                        for op_id, op_code, op_res, op4, op5 in ops:
+                            if res_id == op_res:
+                                self.add_info("  <a href=\"/{}/{}\">{}</a> "\
+                                    "(0x{:X}) - {}\n".format(tp, idx, rec.idx, \
+                                        rec.idx, hlesc(rec.name)))
+                                ru = True
+                                break
+                            #print(op_id, op_code, op_res, op4, op5)
+            self.add_info("<b>Used by objects</b>:\n")
+            usedby(self.sim.objects, "objs")
+            self.add_info("\n<b>Used by scenes</b>:\n")
+            usedby(self.sim.scenes, "scenes")
+        
+        
+    def path_res_view(self, res_id):
         fn = self.sim.res[res_id]
         if fn[-4:].lower() == ".bmp":
             try:
@@ -659,20 +723,20 @@ class App(tkinter.Frame):
                 self.switch_view(0)
                 self.clear_info()
                 self.add_info("Error loading {} - \"{}\" \n\n{}".\
-                    format(res_id, fn, traceback.format_exc()))
+                    format(res_id, hlesc(fn), hlesc(traceback.format_exc())))
             finally:
                 bmpf.close()
         else:
             self.switch_view(0)
             self.clear_info()
             self.add_info("Resource {} - \"{}\" cannot be displayed\n".\
-                format(res_id, fn))
+                format(res_id, hlesc(fn)))
 
     def path_res_status(self):
         self.switch_view(0)
         self.clear_info()
-        self.add_info("<b>Resources</b>: <a href=\"/res\">{}</a>\nFiletypes:\n".format(\
-            len(self.sim.res)))
+        self.add_info("<b>Resources</b>: <a href=\"/res\">{}</a>\n"\
+            "Filetypes:\n".format(len(self.sim.res)))
         fts = {}
         for res in self.sim.res.values():
             fp = res.rfind(".")
@@ -696,7 +760,7 @@ class App(tkinter.Frame):
             # parts
             self.select_lb_item(path[2])
             res_id = self.sim.resord[path[2]]
-            self.path_res_open(res_id)
+            self.path_res_open(res_id, path[3:])
         else:
             self.path_res_status()
 
@@ -707,15 +771,17 @@ class App(tkinter.Frame):
                 lst.append(res_id)
         if self.last_path[:3] != ("res", "flt", path[2]):
             self.update_gui("Resources {} ({})".format(path[2], len(lst)))
+            self.insert_lb_act("All", "/res")
+            self.insert_lb_act("-", None)
             for idx, res_id in enumerate(lst):
                     self.insert_lb_act("{} - {}".format(\
                 res_id, self.sim.res[res_id]), ["res", "flt", path[2], idx])
         # change                
         if len(path) > 3:
             # parts
-            self.select_lb_item(path[3])
+            self.select_lb_item(path[3] + 2)
             res_id = lst[path[3]]
-            self.path_res_open(res_id)
+            self.path_res_open(res_id, path[4:])
         else:
             self.path_res_status()
 
diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
index 41565ebc3..685b4b304 100644
--- a/engines/petka/petka/imgbmp.py
+++ b/engines/petka/petka/imgbmp.py
@@ -99,7 +99,15 @@ class BMPLoader:
                 rgb[off + 1] = (b16 >> 3) & 0b11111100
                 rgb[off + 2] = (b16 >> 8) & 0b11111000
         return rgb
-        
+
+    def load_info(self, f):
+        try:
+            pw, ph, pd = self.load_data_int16(f)
+            self.width = pw
+            self.height = ph
+        except:
+            f.seek(0)
+            self.image = Image.open(f)
         
     def load_data(self, f):
         try:


Commit: c6045ebfa0e0c17c9c4c429e995ccdb21615a128
    https://github.com/scummvm/scummvm-tools/commit/c6045ebfa0e0c17c9c4c429e995ccdb21615a128
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: fix

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 18a1a4b1e..876697ea6 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -671,7 +671,7 @@ class App(tkinter.Frame):
                         self.add_info("internal BMP loader\n  "\
                             "Mode: 16-bit\n  Size: {}x{}".\
                             format(bmp.width, bmp.height))
-                if fn[-4:].lower() == ".flc":
+                elif fn[-4:].lower() == ".flc":
                     self.add_info("<b>FLC animation</b>: ")
                 else:
                     self.add_info("No information availiable")


Commit: 593103b907bc1b9ac35747fa929fc38a67661d65
    https://github.com/scummvm/scummvm-tools/commit/593103b907bc1b9ac35747fa929fc38a67661d65
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Fix switchinf resource filter

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 876697ea6..c7bb87f7b 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -616,42 +616,15 @@ class App(tkinter.Frame):
             return self.path_res_all(path)
         else:
             return self.path_default(path)
-                        
-        if self.last_path[:2] != path[:2]:
-            # 
-            self.switch_view(0)
-            self.update_gui("Resources: {}".format(path[2]))
-            for idx,res_id in self.sim.resord:
-                if self.sim.res[res_id].upper().endswith("." + path[2]):
-                    self.insert_lb_act("{} - {}".format(res_id))
-        
-        if len(self.last_path) == 0 or self.last_path[0] != "res":
-            for idx, name in enumerate(self.sim.invntrord):
-                self.insert_lb_act(name, ["invntr", idx])
 
-        # list resources
-        if self.curr_mode_sub is None:
-            lb = self.update_gui_add_left_listbox("Resources") 
-            for res_id in self.sim.resord:
-                lb.insert(tkinter.END, "{} - {}".format(res_id, \
-                    self.sim.res[res_id]))
-        else:
-            lb = self.update_gui_add_left_listbox("Resources: {}".\
-                format(self.curr_mode_sub))
-            for res_id in self.sim.resord:
-                if self.sim.res[res_id].upper().endswith\
-                    ("." + self.curr_mode_sub):
-                    lb.insert(tkinter.END, "{} - {}".format(res_id, \
-                        self.sim.res[res_id]))
-
-    def path_res_open(self, res_id, mode):
+    def path_res_open(self, pref, res_id, mode):
+        resref = "/" + "/".join([str(x) for x in pref])
         if len(mode) == 0:
             self.switch_view(0)
             fn = self.sim.res[res_id]
             self.clear_info()
             self.add_info("<b>Resource</b>: {} (0x{:X}) - \"{}\"\n\n".\
                 format(res_id, res_id, hlesc(fn)))
-            resref = self.find_path_res(res_id)
             self.add_info("<a href=\"{}/view\">View</a> "\
                 "<a href=\"{}/used\">Used by</a>\n\n".\
                 format(resref, resref))
@@ -685,10 +658,8 @@ class App(tkinter.Frame):
             self.switch_view(0)
             fn = self.sim.res[res_id]
             self.clear_info()
-            resref = self.find_path_res(res_id)
             self.add_info("<b>Resource</b>: <a href=\"{}\">{}</a> (0x{:X}) "\
                 "- \"{}\"\n\n".format(resref, res_id, res_id, hlesc(fn)))
-            
             def usedby(lst, tp):
                 for idx, rec in enumerate(lst):
                     ru = False
@@ -706,8 +677,7 @@ class App(tkinter.Frame):
             usedby(self.sim.objects, "objs")
             self.add_info("\n<b>Used by scenes</b>:\n")
             usedby(self.sim.scenes, "scenes")
-        
-        
+                
     def path_res_view(self, res_id):
         fn = self.sim.res[res_id]
         if fn[-4:].lower() == ".bmp":
@@ -760,7 +730,7 @@ class App(tkinter.Frame):
             # parts
             self.select_lb_item(path[2])
             res_id = self.sim.resord[path[2]]
-            self.path_res_open(res_id, path[3:])
+            self.path_res_open(path[:3], res_id, path[3:])
         else:
             self.path_res_status()
 
@@ -781,7 +751,7 @@ class App(tkinter.Frame):
             # parts
             self.select_lb_item(path[3] + 2)
             res_id = lst[path[3]]
-            self.path_res_open(res_id, path[4:])
+            self.path_res_open(path[:4], res_id, path[4:])
         else:
             self.path_res_status()
 


Commit: c508e1a902b80f1d018ce6642a3a02add1f1610b
    https://github.com/scummvm/scummvm-tools/commit/c508e1a902b80f1d018ce6642a3a02add1f1610b
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: View FLC files (pallette not loaded)

Changed paths:
  A engines/petka/petka/imgflc.py
    engines/petka/p12explore.py
    engines/petka/petka/__init__.py
    engines/petka/petka/imgbmp.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index c7bb87f7b..17b3bd9b1 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -641,11 +641,28 @@ class App(tkinter.Frame):
                             format(bmp.image.mode, \
                                 bmp.image.size[0], bmp.image.size[1]))
                     else:    
-                        self.add_info("internal BMP loader\n  "\
-                            "Mode: 16-bit\n  Size: {}x{}".\
+                        self.add_info("internal BMP loader\n"\
+                            "  Mode: 16-bit\n  Size: {}x{}".\
                             format(bmp.width, bmp.height))
                 elif fn[-4:].lower() == ".flc":
                     self.add_info("<b>FLC animation</b>: ")
+                    flcf = self.sim.fman.read_file_stream(fn)
+                    flc = petka.FLCLoader()
+                    flc.load_info(flcf)
+                    if flc.image:
+                        # PIL
+                        self.add_info("Python Imaging\n")
+                        self.add_info("  Mode:   {}\n  Size:   {}x{}\n"
+                            "  Frames: {}\n  Delay:  {}".\
+                            format(flc.image.mode, \
+                                flc.image.size[0], flc.image.size[1],
+                                flc.frame_num, flc.image.info["duration"]))
+                    else:    
+                        self.add_info("internal FLC loader\n  "\
+                            "  Mode:   P\n  Size:   {}x{}\n"\
+                            "  Frames: {}\nDelay: {}".\
+                            format(flc.width, flc.height, \
+                                flc.frame_num, flc.delay))
                 else:
                     self.add_info("No information availiable")
             except:
@@ -680,27 +697,35 @@ class App(tkinter.Frame):
                 
     def path_res_view(self, res_id):
         fn = self.sim.res[res_id]
-        if fn[-4:].lower() == ".bmp":
-            try:
-                bmpf = self.sim.fman.read_file_stream(fn)
+        try:
+            dataf = self.sim.fman.read_file_stream(fn)
+            if fn[-4:].lower() == ".bmp":
                 bmp = petka.BMPLoader()
-                bmp.load_data(bmpf)
+                bmp.load_data(dataf)
                 self.main_image = \
                     self.make_image(bmp)
                 self.switch_view(1)
                 self.update_canvas()
-            except:
+            elif fn[-4:].lower() == ".flc":
+                flcf = self.sim.fman.read_file_stream(fn)
+                flc = petka.FLCLoader()
+                flc.load_data(dataf)
+                self.main_image = \
+                    self.make_image(flc)
+                self.switch_view(1)
+                self.update_canvas()
+            else:
                 self.switch_view(0)
                 self.clear_info()
-                self.add_info("Error loading {} - \"{}\" \n\n{}".\
-                    format(res_id, hlesc(fn), hlesc(traceback.format_exc())))
-            finally:
-                bmpf.close()
-        else:
+                self.add_info("Resource {} - \"{}\" cannot be displayed\n".\
+                    format(res_id, hlesc(fn)))
+        except:
             self.switch_view(0)
             self.clear_info()
-            self.add_info("Resource {} - \"{}\" cannot be displayed\n".\
-                format(res_id, hlesc(fn)))
+            self.add_info("Error loading {} - \"{}\" \n\n{}".\
+                format(res_id, hlesc(fn), hlesc(traceback.format_exc())))
+        finally:
+            dataf.close()
 
     def path_res_status(self):
         self.switch_view(0)
diff --git a/engines/petka/petka/__init__.py b/engines/petka/petka/__init__.py
index 712beca4a..42388d6d6 100644
--- a/engines/petka/petka/__init__.py
+++ b/engines/petka/petka/__init__.py
@@ -4,3 +4,4 @@ class EngineError(Exception): pass
 from .engine import Engine, OPCODES
 from .fman import FileManager
 from .imgbmp import BMPLoader
+from .imgflc import FLCLoader
diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
index 685b4b304..631c367a0 100644
--- a/engines/petka/petka/imgbmp.py
+++ b/engines/petka/petka/imgbmp.py
@@ -74,22 +74,6 @@ class BMPLoader:
         b16arr = array.array("H") # unsigned short
         b16arr.frombytes(pd)
         b16arr.byteswap()
-        
-        #rgb = array.array("B")
-        #for b16 in b16arr:
-            #rgb.append((b16 >> 5) & 0b11111000)
-            #rgb.append((b16 << 5) & 0b11100000 | (b16 >> 11) & 0b00011100)
-            #rgb.append((b16 << 0) &0b11111000) 
-        #    rgb.append((b16 << 3) & 0b11111000)
-        #    rgb.append((b16 >> 3) & 0b11111100)
-        #    rgb.append((b16 >> 8) & 0b11111000)
-        ## Y-mirror
-        #newrgb = array.array("B")
-        #for i in range(ph):
-        #    off = (ph - i - 1) * pw * 3
-        #    newrgb += rgb[off:off + pw * 3]
-        #return newrgb
-        
         rgb = array.array("B", [0] * pw * ph * 3)
         for j in range(ph):
             for i in range(pw):
@@ -113,20 +97,6 @@ class BMPLoader:
         try:
             pw, ph, pd = self.load_data_int16(f)
             if Image:
-                # reload fixed
-                #f.seek(0)
-                #d = io.BytesIO()
-                #d.write(f.read(10))
-                #data_off = struct.unpack("<I", f.read(4))[0]
-                #d.write(struct.pack("<I", data_off - 1))
-                #d.write(f.read(data_off - 14))
-                #fmt = "{}H".format(pw * ph)
-                #data = struct.unpack(">" + fmt, f.read(pw * ph * 2))
-                #d.write(struct.pack("<" + fmt, *data))
-                #d.write(f.read(pw * ph * 2))
-                #d.write(b"\xFF" * 10)
-                #d.seek(0)
-                #self.image = Image.open(d)
                 pd = self.pixelswap16(pw, ph, pd).tobytes()
                 self.image = Image.frombytes("RGB", (pw, ph), pd) 
             else:
diff --git a/engines/petka/petka/imgflc.py b/engines/petka/petka/imgflc.py
new file mode 100644
index 000000000..63040d7a8
--- /dev/null
+++ b/engines/petka/petka/imgflc.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+# romiq.kh at gmail.com, 2014
+
+import array, struct, io
+
+from . import EngineError
+
+try:
+    from PIL import Image
+except ImportError:
+    Image = None
+
+class FLCLoader:
+    def __init__(self):
+        self.rgb = None
+        self.image = None
+        self.width = 0
+        self.height = 0
+        self.frame_num = 0
+        self.delay = 0
+        
+
+    def load_info(self, f):
+        self.image = Image.open(f)
+        self.frame_num = 1
+        try:
+            while 1:
+                self.image.seek(self.image.tell() + 1)
+                self.frame_num += 1
+        except EOFError:
+            pass # end of sequence
+        
+    def load_data(self, f):
+        self.image = Image.open(f)
+            


Commit: 73d0812642bafe94831b921d90996171520f7089
    https://github.com/scummvm/scummvm-tools/commit/73d0812642bafe94831b921d90996171520f7089
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Messages works

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 17b3bd9b1..e0bf5ab67 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -149,6 +149,8 @@ class App(tkinter.Frame):
         self.path_handler["scenes"] = self.path_objs_scenes
         self.path_handler["names"] = self.path_names
         self.path_handler["invntr"] = self.path_invntr
+        self.path_handler["msgs"] = self.path_msgs
+        self.path_handler["dlgs"] = self.path_dlgs
         self.path_handler["test"] = self.path_test
         
         self.update_after()
@@ -179,24 +181,30 @@ class App(tkinter.Frame):
                 label = "Outline")
         self.menuedit.add_separator()
         self.menuedit.add_command(
-                command = lambda: self.open_path(["parts"]),
+                command = lambda: self.open_path("/parts"),
                 label = "Select part")
         self.menuedit.add_separator()
         self.menuedit.add_command(
-                command = lambda: self.open_path(["res"]),
+                command = lambda: self.open_path("/res"),
                 label = "Resources")
         self.menuedit.add_command(
-                command = lambda: self.open_path(["objs"]),
+                command = lambda: self.open_path("/objs"),
                 label = "Objects")
         self.menuedit.add_command(
-                command = lambda: self.open_path(["scenes"]),
+                command = lambda: self.open_path("/scenes"),
                 label = "Scenes")
         self.menuedit.add_command(
-                command = lambda: self.open_path(["names"]),
+                command = lambda: self.open_path("/names"),
                 label = "Names")
         self.menuedit.add_command(
-                command = lambda: self.open_path(["invntr"]),
+                command = lambda: self.open_path("/invntr"),
                 label = "Invntr")
+        self.menuedit.add_command(
+                command = lambda: self.open_path("/msgs"),
+                label = "Messages")
+        self.menuedit.add_command(
+                command = lambda: self.open_path("/dlgs"),
+                label = "Dialog groups")
 
     def update_after(self):
         if not self.need_update:
@@ -541,16 +549,20 @@ class App(tkinter.Frame):
     def path_info_outline(self):
         self.add_info("Current part {} chapter {}\n\n".\
                 format(self.sim.curr_part, self.sim.curr_chap))
-        self.add_info("  Resources: <a href=\"/res\">{}</a>\n".\
+        self.add_info("  Resources:     <a href=\"/res\">{}</a>\n".\
             format(len(self.sim.res)))
-        self.add_info("  Objects:   <a href=\"/objs\">{}</a>\n".\
+        self.add_info("  Objects:       <a href=\"/objs\">{}</a>\n".\
             format(len(self.sim.objects)))
-        self.add_info("  Scenes:    <a href=\"/scenes\">{}</a>\n".\
+        self.add_info("  Scenes:        <a href=\"/scenes\">{}</a>\n".\
             format(len(self.sim.scenes)))
-        self.add_info("  Names:     <a href=\"/names\">{}</a>\n".\
+        self.add_info("  Names:         <a href=\"/names\">{}</a>\n".\
             format(len(self.sim.names)))
-        self.add_info("  Invntr:    <a href=\"/invntr\">{}</a>\n".\
+        self.add_info("  Invntr:        <a href=\"/invntr\">{}</a>\n".\
             format(len(self.sim.invntr)))
+        self.add_info("  Messages       <a href=\"/msgs\">{}</a>\n".\
+            format(len(self.sim.msgs)))
+        self.add_info("  Dialog groups: <a href=\"/dlgs\">{}</a>\n".\
+            format(len(self.sim.dlgs)))
     
 
     def path_default(self, path):
@@ -566,12 +578,14 @@ class App(tkinter.Frame):
         self.path_info_outline()
         if self.sim is not None:
             acts = [
-                ("Parts ({})".format(len(self.sim.parts)), ["parts"]),
-                ("Resources ({})".format(len(self.sim.res)), ["res"]),
-                ("Objects ({})".format(len(self.sim.objects)), ["objs"]),
-                ("Scenes ({})".format(len(self.sim.scenes)), ["scenes"]),
-                ("Names ({})".format(len(self.sim.names)), ["names"]),
-                ("Invntr ({})".format(len(self.sim.invntr)), ["invntr"]),
+                ("Parts ({})".format(len(self.sim.parts)), "/parts"),
+                ("Resources ({})".format(len(self.sim.res)), "/res"),
+                ("Objects ({})".format(len(self.sim.objects)), "/objs"),
+                ("Scenes ({})".format(len(self.sim.scenes)), "/scenes"),
+                ("Names ({})".format(len(self.sim.names)), "/names"),
+                ("Invntr ({})".format(len(self.sim.invntr)), "/invntr"),
+                ("Messages ({})".format(len(self.sim.msgs)), "/msgs"),
+                ("Dialog groups ({})".format(len(self.sim.dlgs)), "/dlgs"),
                 ("-", None),
                 ("Test image", ["test", "image"]),
                 ("Test info", ["test","info"]),
@@ -889,6 +903,11 @@ class App(tkinter.Frame):
                         format(self.find_path_res(res_id), res_id, res_id, \
                         hlesc(self.sim.res[res_id])))
             
+            self.add_info("\n<b>Messages</b>:\n")
+            for msg in self.sim.msgs:
+                if msg.obj.idx != rec.idx: continue
+                self.add_info("  <a href=\"/msgs/{}\">{}</a> (0x{:X}) - {}\n".\
+                    format(msg.idx, msg.idx, msg.idx, hlesc(msg.capt)))
 
     def path_names(self, path):
         self.switch_view(0)
@@ -946,6 +965,57 @@ class App(tkinter.Frame):
                         "- {}\n".format(idx, obj.idx, obj.idx, \
                         hlesc(obj.name)))
 
+    def path_msgs(self, path):
+        self.switch_view(0)
+        if self.last_path[:1] != ("msgs",):
+            self.update_gui("Messages ({})".format(len(self.sim.msgs)))
+            for idx, msg in enumerate(self.sim.msgs):
+                capt = msg.capt
+                if len(capt) > 25:
+                    capt = capt[:25] + "|"
+                self.insert_lb_act("{} - {}".format(msg.idx, capt), \
+                    [self.curr_path[0], idx])
+        # change
+        msg = None
+        if len(path) > 1:
+            # parts
+            self.select_lb_item(path[1])
+            msg = self.sim.msgs[path[1]]
+        # display
+        self.clear_info()
+        if not msg:
+            self.add_info("Select <b>message</b>\n")
+        else:
+            # msg info
+            self.add_info("<b>Message</b>: {}\n".format(path[1]))
+            self.add_info("  wav:    {}\n".format(msg.wav))
+            self.add_info("  object: <a href=\"{}\">{}</a> (0x{:X}) - {}\n".\
+                format(self.find_path_obj(msg.arg1), msg.arg1, msg.arg1, \
+                msg.obj.name))
+            self.add_info("  arg2:   {} (0x{:X})\n".format(msg.arg2, msg.arg2))
+            self.add_info("  arg3:   {} (0x{:X})\n".format(msg.arg3, msg.arg3))
+            self.add_info("\n{}".format(hlesc(msg.capt)))
+
+    def path_dlgs(self, path):
+        self.switch_view(0)
+        if self.last_path[:1] != ("dlgs",):
+            self.update_gui("Dialog groups ({})".format(len(self.sim.dlgs)))
+            for idx, dlg in enumerate(self.sim.dlgs):
+                self.insert_lb_act(name, ["dlgs", idx])
+        # change
+        name = None
+        if len(path) > 1:
+            # parts
+            self.select_lb_item(path[1])
+            dlg = self.sim.dlgs[path[1]]
+        # display
+        self.clear_info()
+        if not name:
+            self.add_info("Select <b>dialog group</b>\n")
+        else:
+            # dlg info
+            self.add_info("<b>Dialog group</b>: {}\n".format(path[1]))
+
     def path_test(self, path):
         self.update_gui("Test {}".format(path[1]))
         self.insert_lb_act("Outline", [])
@@ -969,7 +1039,7 @@ class App(tkinter.Frame):
     def open_data_from(self, folder):
         self.sim = petka.Engine()
         self.sim.load_data(folder, "cp1251")
-        self.sim.open_part(0, 0)
+        self.sim.open_part(1, 0)
 
 def main():
     root = tkinter.Tk()
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index d6fc1b189..0979f2d3f 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -74,6 +74,15 @@ class ScrObject:
         self.idx = idx
         self.name = name
 
+class MsgObject:
+    def __init__(self, idx, wav, arg1, arg2, arg3):
+        self.idx = idx
+        self.wav = wav
+        self.arg1 = arg1
+        self.arg2 = arg2
+        self.arg3 = arg3
+        self.capt = None
+
 class Engine:
     def __init__(self):
         self.fman = None
@@ -205,8 +214,12 @@ class Engine:
             if not pf: continue
             if strf in ini:
                 self.fman.load_store(ini[strf], 1)
-        # load script
+        # load script.dat, backgrnd.bg and resources.qrc
         self.load_script()
+        # load names & invntr
+        self.load_names()
+        # load dialogs
+        self.load_dialogs()
         
     def load_script(self):
         self.objects = []
@@ -269,13 +282,14 @@ class Engine:
                     obj = self.obj_idx[ref[0]]
                     scn.refs.append([obj] + list(ref[1:]))
                 else:
-                    raise EngineError("DEBUG: Object ID = 0x{:x} not found".\
+                    raise EngineError("DEBUG: Scene ref 0x{:x} not found".\
                         format(obj[0]))
                         
         f = self.fman.read_file_stream(self.curr_path + "resource.qrc")
         self.res, self.resord = self.parse_res(f)
         f.close()
         
+    def load_names(self):
         self.names = {}
         self.namesord = []
         fp = self.curr_path + "names.ini"
@@ -296,4 +310,39 @@ class Engine:
             self.invntrord = ini["__order__"]["ALL"]
             f.close()
         
+    def load_dialogs(self):
+        self.msgs = []
+        # DIALOGUES.LOD
+        fp = self.curr_path + "dialogue.lod"
+        if self.fman.exists(fp):
+            f = self.fman.read_file_stream(fp)
+            try:
+                temp = f.read(4)
+                num_msg = struct.unpack_from("<I", temp)[0]
+                for i in range(num_msg):
+                    temp = f.read(24)
+                    arg1, wav, arg2, arg3 = struct.unpack_from("<I12sII", temp)
+                    msg = MsgObject(len(self.msgs), \
+                        wav.decode(self.enc).strip(), arg1, arg2, arg3)
+                    # scan objects
+                    msg.obj = None
+                    for obj in self.objects:
+                        if obj.idx == arg1:
+                            msg.obj = obj
+                            break
+                    if not msg.obj:
+                        raise EngineError("DEBUG: Message ref = 0x{:x} not found".\
+                        format(obj[0]))
+                    self.msgs.append(msg)
+                for i, capt in enumerate(f.read().split(b"\x00")):
+                    if i < len(self.msgs):
+                        self.msgs[i].capt = capt.decode(self.enc)
+            finally:
+                f.close()
+
+        self.dlgs = []
+        # DIALOGUES.FIX
+        fp = self.curr_path + "dialogue.fix"
+        if self.fman.exists(fp):
+            f = self.fman.read_file_stream(fp)
 


Commit: f7fa014126386c5736640795821edee08191b9ba
    https://github.com/scummvm/scummvm-tools/commit/f7fa014126386c5736640795821edee08191b9ba
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Messages works

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index e0bf5ab67..69bf2e2c9 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -903,11 +903,12 @@ class App(tkinter.Frame):
                         format(self.find_path_res(res_id), res_id, res_id, \
                         hlesc(self.sim.res[res_id])))
             
-            self.add_info("\n<b>Messages</b>:\n")
-            for msg in self.sim.msgs:
-                if msg.obj.idx != rec.idx: continue
-                self.add_info("  <a href=\"/msgs/{}\">{}</a> (0x{:X}) - {}\n".\
-                    format(msg.idx, msg.idx, msg.idx, hlesc(msg.capt)))
+            if isobj:
+                self.add_info("\n<b>Messages</b>:\n")
+                for msg in self.sim.msgs:
+                    if msg.obj.idx != rec.idx: continue
+                    self.add_info("  <a href=\"/msgs/{}\">{}</a> (0x{:X}) - {}\n".\
+                        format(msg.idx, msg.idx, msg.idx, hlesc(msg.capt)))
 
     def path_names(self, path):
         self.switch_view(0)


Commit: ee14e7f6c33e74976e17f4fecd2a6802fa32eb1e
    https://github.com/scummvm/scummvm-tools/commit/ee14e7f6c33e74976e17f4fecd2a6802fa32eb1e
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Dialogue.fix groups (partial)

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 69bf2e2c9..86f3f986b 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -975,7 +975,7 @@ class App(tkinter.Frame):
                 if len(capt) > 25:
                     capt = capt[:25] + "|"
                 self.insert_lb_act("{} - {}".format(msg.idx, capt), \
-                    [self.curr_path[0], idx])
+                    ["msgs", idx])
         # change
         msg = None
         if len(path) > 1:
@@ -1001,21 +1001,34 @@ class App(tkinter.Frame):
         self.switch_view(0)
         if self.last_path[:1] != ("dlgs",):
             self.update_gui("Dialog groups ({})".format(len(self.sim.dlgs)))
-            for idx, dlg in enumerate(self.sim.dlgs):
-                self.insert_lb_act(name, ["dlgs", idx])
+            for idx, grp in enumerate(self.sim.dlgs):
+                self.insert_lb_act("{} (0x{:X})".format(grp.idx, grp.idx), \
+                    ["dlgs", idx])
         # change
-        name = None
+        grp = None
         if len(path) > 1:
             # parts
             self.select_lb_item(path[1])
-            dlg = self.sim.dlgs[path[1]]
+            grp = self.sim.dlgs[path[1]]
         # display
         self.clear_info()
-        if not name:
+        if not grp:
             self.add_info("Select <b>dialog group</b>\n")
         else:
-            # dlg info
-            self.add_info("<b>Dialog group</b>: {}\n".format(path[1]))
+            # grp info
+            self.add_info("<b>Dialog group</b>: {} (0x{:X})\n".format(\
+                grp.idx, grp.idx))
+            self.add_info("  arg1: {} (0x{:X})\n\n".format(grp.arg1, grp.arg1))
+            self.add_info("<b>Dialog sets<b>: {}\n".format(len(grp.sets)))
+            for idx, dlgset in enumerate(grp.sets):
+                self.add_info("  {}) <u>0x{:X} 0x{:X} 0x{:X}</u>, dlgs: {}\n".\
+                    format(idx, dlgset.arg1, dlgset.arg2, dlgset.arg3, \
+                        len(dlgset.dlgs)))
+                for didx, dlg in enumerate(dlgset.dlgs):
+                    self.add_info("    {}) 0x{:X} 0x{:X}, ops: {}\n".\
+                        format(didx, dlg.arg1, dlg.arg2, len(dlg.ops)))
+                
+            
 
     def path_test(self, path):
         self.update_gui("Test {}".format(path[1]))
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 0979f2d3f..25f230aec 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -83,6 +83,28 @@ class MsgObject:
         self.arg3 = arg3
         self.capt = None
 
+class DlgGrpObject:
+    def __init__(self, idx, num_sets, arg1):
+        self.idx = idx
+        self.num_sets = num_sets
+        self.arg1 = arg1
+        self.sets = None
+
+class DlgSetObject:
+    def __init__(self, num_dlgs, arg1, arg2, arg3):
+        self.num_dlgs = num_dlgs
+        self.arg1 = arg1
+        self.arg2 = arg2
+        self.arg3 = arg3
+        self.dlgs = None
+
+class DlgObject:
+    def __init__(self, op_start, arg1, arg2):
+        self.op_start = op_start
+        self.arg1 = arg1
+        self.arg2 = arg2
+        self.ops = None
+        
 class Engine:
     def __init__(self):
         self.fman = None
@@ -345,4 +367,36 @@ class Engine:
         fp = self.curr_path + "dialogue.fix"
         if self.fman.exists(fp):
             f = self.fman.read_file_stream(fp)
+            try:
+                temp = f.read(4)
+                num_grps = struct.unpack_from("<I", temp)[0]
+                for i in range(num_grps):
+                    temp = f.read(12)
+                    idx, num_sets, arg1 = struct.unpack_from("<III", temp)
+                    grp = DlgGrpObject(idx, num_sets, arg1)
+                    self.dlgs.append(grp)
+                for grp in self.dlgs:
+                    grp.sets = []
+                    for i in range(grp.num_sets):
+                        temp = f.read(16)
+                        arg1, num_dlgs, arg2, arg3 = \
+                            struct.unpack_from("<4I", temp)
+                        dlgset = DlgSetObject(num_dlgs, arg1, arg2, arg3)
+                        grp.sets.append(dlgset)
+                    for dlgset in grp.sets:
+                        dlgset.dlgs = []
+                        for i in range(dlgset.num_dlgs):
+                            temp = f.read(12)
+                            op_start, arg1, arg2 = \
+                                struct.unpack_from("<3I", temp)
+                            dlg = DlgObject(op_start, arg1, arg2)
+                            dlg.ops = []
+                            dlgset.dlgs.append(dlg)
+                temp = f.read(4)
+                num_ops = struct.unpack_from("<I", temp)[0]
+                for i in range(num_ops):
+                    temp = f.read(4)
+                    ref, arg, code  = struct.unpack_from("<HBB", temp)
+            finally:
+                f.close()
 


Commit: 423e063c13f3065a75994f1714c0b87139397a1e
    https://github.com/scummvm/scummvm-tools/commit/423e063c13f3065a75994f1714c0b87139397a1e
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: dialogues works

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 86f3f986b..368d716eb 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1025,9 +1025,21 @@ class App(tkinter.Frame):
                     format(idx, dlgset.arg1, dlgset.arg2, dlgset.arg3, \
                         len(dlgset.dlgs)))
                 for didx, dlg in enumerate(dlgset.dlgs):
-                    self.add_info("    {}) 0x{:X} 0x{:X}, ops: {}\n".\
+                    self.add_info("    {}) <i>0x{:X} 0x{:X}</i>, ops: {}\n".\
                         format(didx, dlg.arg1, dlg.arg2, len(dlg.ops)))
-                
+                    for op in dlg.ops:
+                        cmt = ""
+                        opref = "0x{:X}".format(op.ref)
+                        opcode = "OP_{:02X}".format(op.opcode)
+                        if op.opcode == 7:
+                            opcode = "PLAY"
+                            if op.msg:
+                                opref = "<a href=\"/msgs/{}\">{}</a>".format(\
+                                    op.ref, op.ref)
+                                cmt = " / (0x{:X}) - {}".\
+                                    format(op.ref, hlesc(op.msg.capt))
+                        self.add_info("      {} 0x{:X} {}{}\n".\
+                            format(opcode, op.arg, opref, hlesc(cmt)))
             
 
     def path_test(self, path):
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 25f230aec..8b8006e86 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -104,6 +104,13 @@ class DlgObject:
         self.arg1 = arg1
         self.arg2 = arg2
         self.ops = None
+
+class DlgOpObject:
+    def __init__(self, opcode, arg, ref):
+        self.opcode = opcode
+        self.arg = arg
+        self.ref = ref
+        self.msg = None
         
 class Engine:
     def __init__(self):
@@ -347,11 +354,7 @@ class Engine:
                     msg = MsgObject(len(self.msgs), \
                         wav.decode(self.enc).strip(), arg1, arg2, arg3)
                     # scan objects
-                    msg.obj = None
-                    for obj in self.objects:
-                        if obj.idx == arg1:
-                            msg.obj = obj
-                            break
+                    msg.obj = self.obj_idx.get(arg1, None)
                     if not msg.obj:
                         raise EngineError("DEBUG: Message ref = 0x{:x} not found".\
                         format(obj[0]))
@@ -363,6 +366,7 @@ class Engine:
                 f.close()
 
         self.dlgs = []
+        self.dlgops = []
         # DIALOGUES.FIX
         fp = self.curr_path + "dialogue.fix"
         if self.fman.exists(fp):
@@ -375,6 +379,7 @@ class Engine:
                     idx, num_sets, arg1 = struct.unpack_from("<III", temp)
                     grp = DlgGrpObject(idx, num_sets, arg1)
                     self.dlgs.append(grp)
+                opref = {}
                 for grp in self.dlgs:
                     grp.sets = []
                     for i in range(grp.num_sets):
@@ -389,14 +394,33 @@ class Engine:
                             temp = f.read(12)
                             op_start, arg1, arg2 = \
                                 struct.unpack_from("<3I", temp)
+                            if op_start in opref:
+                                raise EngineError(
+                                    "Multiple dialog opcodes reference")
                             dlg = DlgObject(op_start, arg1, arg2)
-                            dlg.ops = []
+                            opref[op_start] = dlg
+                            dlg.ops = None
                             dlgset.dlgs.append(dlg)
                 temp = f.read(4)
                 num_ops = struct.unpack_from("<I", temp)[0]
                 for i in range(num_ops):
                     temp = f.read(4)
                     ref, arg, code  = struct.unpack_from("<HBB", temp)
+                    dlgop = DlgOpObject(code, arg, ref)
+                    if ref < len(self.msgs):
+                        dlgop.msg = self.msgs[ref]
+                    self.dlgops.append(dlgop)
+                dlg = None
+                oparr = []
+                for idx, oprec in enumerate(self.dlgops):
+                    if idx in opref:
+                        if len(oparr) > 0:
+                            dlg.ops = oparr
+                            oparr = []
+                        dlg = opref[idx]
+                    oparr.append(oprec)    
+                if len(oparr) > 0:
+                    dlg.ops = oparr
             finally:
                 f.close()
 


Commit: d3f052e1bd65761f574bbb178f45805c523164c7
    https://github.com/scummvm/scummvm-tools/commit/d3f052e1bd65761f574bbb178f45805c523164c7
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: links from messages to dialoggroups

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 368d716eb..7348b0df8 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -546,6 +546,12 @@ class App(tkinter.Frame):
                 return "/invntr/{}".format(idx)
         return "/no_invntr/{}".format(key)
 
+    def find_path_dlggrp(self, grp_idx):
+        for idx, grp in enumerate(self.sim.dlgs):
+            if grp.idx == grp_idx:
+                return "/dlgs/{}".format(idx)
+        return "/no_dlgs/{}".format(grp_idx)
+
     def path_info_outline(self):
         self.add_info("Current part {} chapter {}\n\n".\
                 format(self.sim.curr_part, self.sim.curr_chap))
@@ -995,8 +1001,21 @@ class App(tkinter.Frame):
                 msg.obj.name))
             self.add_info("  arg2:   {} (0x{:X})\n".format(msg.arg2, msg.arg2))
             self.add_info("  arg3:   {} (0x{:X})\n".format(msg.arg3, msg.arg3))
-            self.add_info("\n{}".format(hlesc(msg.capt)))
+            self.add_info("\n{}\n".format(hlesc(msg.capt)))
 
+            self.add_info("\n<b>Used by dialog groups</b>:\n")
+            for grp in self.sim.dlgs:
+                for dlgset in grp.sets:
+                    for dlg in dlgset.dlgs:
+                        for op in dlg.ops:
+                            if not op.msg: continue
+                            if op.msg.idx == msg.idx and op.opcode == 7:
+                                self.add_info("  <a href=\"{}\">{}</a>\n".\
+                                    format(self.find_path_dlggrp(grp.idx), 
+                                        grp.idx))
+                                
+                
+                
     def path_dlgs(self, path):
         self.switch_view(0)
         if self.last_path[:1] != ("dlgs",):
@@ -1034,12 +1053,15 @@ class App(tkinter.Frame):
                         if op.opcode == 7:
                             opcode = "PLAY"
                             if op.msg:
-                                opref = "<a href=\"/msgs/{}\">{}</a>".format(\
+                                opref = "<a href=\"/msgs/{}\">{}</a>".format(
                                     op.ref, op.ref)
-                                cmt = " / (0x{:X}) - {}".\
-                                    format(op.ref, hlesc(op.msg.capt))
+                                objref = "<a href=\"{}\">{}</a>".format(
+                                    self.find_path_obj(op.msg.obj.idx),
+                                    op.msg.obj.idx)                                
+                                cmt = " / (0x{:X}) - {}, {}".\
+                                    format(op.ref, objref, hlesc(op.msg.capt))
                         self.add_info("      {} 0x{:X} {}{}\n".\
-                            format(opcode, op.arg, opref, hlesc(cmt)))
+                            format(opcode, op.arg, opref, cmt))
             
 
     def path_test(self, path):


Commit: a1665895e4e850103584d67c36cea85e579c567f
    https://github.com/scummvm/scummvm-tools/commit/a1665895e4e850103584d67c36cea85e579c567f
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: refactor

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 7348b0df8..7b02e6063 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -25,6 +25,10 @@ APPNAME = "P1&2 Explorer"
 def hlesc(value):
     return value.replace("\\", "\\\\").replace("<", "\\<").replace(">", "\\>")
 
+def fmt_opcode(opcode):
+    return petka.OPCODES.get(opcode, ["OP_{:X}".format(opcode)])[0]
+
+
 # thanx to http://effbot.org/zone/tkinter-text-hyperlink.htm
 class HyperlinkManager:
     def __init__(self, text):
@@ -874,14 +878,13 @@ class App(tkinter.Frame):
             resused = []
             self.add_info("\n<b>Handlers</b>: {}\n".format(len(rec.acts)))
             for idx, (act_id, act_cond, act_arg, ops) in enumerate(rec.acts):
-                msg = petka.OPCODES.get(act_id, ["OP_{:X}".format(act_id)])[0]
+                msg = fmt_opcode(act_id)
                 if act_cond != 0xff or act_arg != 0xffff:
                     msg += " 0x{:02X} 0x{:04X}".format(act_cond, act_arg)
                 self.add_info("  {}) <u>on {}</u>, ops: {}\n".format(\
                     idx, msg, len(ops)))
                 for oidx, op in enumerate(ops):
-                    msg = petka.OPCODES.get(op[1], ["OP_{:X}".format(op[1])])[0]
-                    self.add_info("    {}) {} ".format(oidx, msg))
+                    self.add_info("    {}) {} ".format(oidx, fmt_opcode(op[1])))
                     if op[0] == rec.idx:
                         self.add_info("THIS")
                     else:
@@ -969,7 +972,7 @@ class App(tkinter.Frame):
             for idx, obj in enumerate(self.sim.objects):
                 if obj.name == name:
                     self.add_info("  <a href=\"/objs/{}\">{}</a> (0x{:X}) "\
-                        "- {}\n".format(idx, obj.idx, obj.idx, \
+                        "- {}\n".format(idx, obj.idx, obj.idx,
                         hlesc(obj.name)))
 
     def path_msgs(self, path):
@@ -980,7 +983,7 @@ class App(tkinter.Frame):
                 capt = msg.capt
                 if len(capt) > 25:
                     capt = capt[:25] + "|"
-                self.insert_lb_act("{} - {}".format(msg.idx, capt), \
+                self.insert_lb_act("{} - {}".format(msg.idx, capt),
                     ["msgs", idx])
         # change
         msg = None
@@ -997,8 +1000,8 @@ class App(tkinter.Frame):
             self.add_info("<b>Message</b>: {}\n".format(path[1]))
             self.add_info("  wav:    {}\n".format(msg.wav))
             self.add_info("  object: <a href=\"{}\">{}</a> (0x{:X}) - {}\n".\
-                format(self.find_path_obj(msg.arg1), msg.arg1, msg.arg1, \
-                msg.obj.name))
+                format(self.find_path_obj(msg.arg1), msg.arg1, msg.arg1,
+                    msg.obj.name))
             self.add_info("  arg2:   {} (0x{:X})\n".format(msg.arg2, msg.arg2))
             self.add_info("  arg3:   {} (0x{:X})\n".format(msg.arg3, msg.arg3))
             self.add_info("\n{}\n".format(hlesc(msg.capt)))
@@ -1021,7 +1024,7 @@ class App(tkinter.Frame):
         if self.last_path[:1] != ("dlgs",):
             self.update_gui("Dialog groups ({})".format(len(self.sim.dlgs)))
             for idx, grp in enumerate(self.sim.dlgs):
-                self.insert_lb_act("{} (0x{:X})".format(grp.idx, grp.idx), \
+                self.insert_lb_act("{} (0x{:X})".format(grp.idx, grp.idx),
                     ["dlgs", idx])
         # change
         grp = None


Commit: 58398995a414167a4830323f2d486e8896df53b8
    https://github.com/scummvm/scummvm-tools/commit/58398995a414167a4830323f2d486e8896df53b8
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: refactor

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 7b02e6063..c14e35004 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1041,7 +1041,7 @@ class App(tkinter.Frame):
             self.add_info("<b>Dialog group</b>: {} (0x{:X})\n".format(\
                 grp.idx, grp.idx))
             self.add_info("  arg1: {} (0x{:X})\n\n".format(grp.arg1, grp.arg1))
-            self.add_info("<b>Dialog sets<b>: {}\n".format(len(grp.sets)))
+            self.add_info("<b>Dialog handlers<b>: {}\n".format(len(grp.sets)))
             for idx, dlgset in enumerate(grp.sets):
                 self.add_info("  {}) <u>0x{:X} 0x{:X} 0x{:X}</u>, dlgs: {}\n".\
                     format(idx, dlgset.arg1, dlgset.arg2, dlgset.arg3, \
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 8b8006e86..7d6b07643 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -73,6 +73,7 @@ class ScrObject:
     def __init__(self, idx, name):
         self.idx = idx
         self.name = name
+        self.acts = None
 
 class MsgObject:
     def __init__(self, idx, wav, arg1, arg2, arg3):
@@ -88,14 +89,15 @@ class DlgGrpObject:
         self.idx = idx
         self.num_sets = num_sets
         self.arg1 = arg1
-        self.sets = None
+        self.acts = None
 
-class DlgSetObject:
-    def __init__(self, num_dlgs, arg1, arg2, arg3):
+class DlgActObject:
+    def __init__(self, num_dlgs, opcode, ref, arg1, arg2):
         self.num_dlgs = num_dlgs
+        self.opcode = opcode
+        self.ref = ref
         self.arg1 = arg1
         self.arg2 = arg2
-        self.arg3 = arg3
         self.dlgs = None
 
 class DlgObject:


Commit: ac443fbabfdc4e5af3f7e80d8fd9ce2a6f1ea02c
    https://github.com/scummvm/scummvm-tools/commit/ac443fbabfdc4e5af3f7e80d8fd9ce2a6f1ea02c
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: links from dialog acts to objects

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index c14e35004..64a1fb432 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -26,7 +26,10 @@ def hlesc(value):
     return value.replace("\\", "\\\\").replace("<", "\\<").replace(">", "\\>")
 
 def fmt_opcode(opcode):
-    return petka.OPCODES.get(opcode, ["OP_{:X}".format(opcode)])[0]
+    return petka.OPCODES.get(opcode, ["OP{:04X}".format(opcode)])[0]
+
+def fmt_hl(loc, desc):
+    return "<a href=\"{}\">{}</a>".format(loc, desc)
 
 
 # thanx to http://effbot.org/zone/tkinter-text-hyperlink.htm
@@ -158,9 +161,10 @@ class App(tkinter.Frame):
         self.path_handler["test"] = self.path_test
         
         self.update_after()
-        self.open_path("")
+        #self.open_path("")
         #self.open_path(self.find_path_scene(36))
         #self.open_path(["res", "flt", "BMP", 7])
+        self.open_path(["dlgs", 6])
 
     def create_menu(self):
         self.menubar = tkinter.Menu(self.master)
@@ -537,6 +541,26 @@ class App(tkinter.Frame):
             if rec.idx == rec_idx:
                 return "/scenes/{}".format(idx)
         return "/no_obj_scene/{}".format(rec_idx)
+
+    def fmt_hl_rec(self, lst, pref, rec_idx, full = False):
+        for idx, rec in enumerate(lst):
+            if rec.idx == rec_idx:
+                fmt = fmt_hl("/{}/{}".format(pref, idx), str(rec_idx))
+                if full:
+                    fmt += " (0x{:X}) - {}".format(rec.idx, hlesc(rec.name))
+                return fmt
+        return "{} (0x{:X})".format(rec_idx, rec_idx)
+        
+    def fmt_hl_obj(self, obj_idx, full = False):
+        return self.fmt_hl_rec(self.sim.objects, "objs", obj_idx, full)
+        
+    def fmt_hl_scene(self, scn_idx, full = False):
+        return self.fmt_hl_rec(self.sim.scenes, "scene", scn_idx, full)
+
+    def fmt_hl_obj_scene(self, rec_idx, full = False):
+        if rec_idx in self.sim.obj_idx:
+            return self.fmt_hl_rec(self.sim.objects, "objs", obj_idx, full)
+        return self.fmt_hl_rec(self.sim.scenes, "scene", scn_idx, full)
         
     def find_path_name(self, key):
         for idx, name in enumerate(self.sim.namesord):
@@ -1041,12 +1065,13 @@ class App(tkinter.Frame):
             self.add_info("<b>Dialog group</b>: {} (0x{:X})\n".format(\
                 grp.idx, grp.idx))
             self.add_info("  arg1: {} (0x{:X})\n\n".format(grp.arg1, grp.arg1))
-            self.add_info("<b>Dialog handlers<b>: {}\n".format(len(grp.sets)))
-            for idx, dlgset in enumerate(grp.sets):
-                self.add_info("  {}) <u>0x{:X} 0x{:X} 0x{:X}</u>, dlgs: {}\n".\
-                    format(idx, dlgset.arg1, dlgset.arg2, dlgset.arg3, \
-                        len(dlgset.dlgs)))
-                for didx, dlg in enumerate(dlgset.dlgs):
+            self.add_info("<b>Dialog handlers<b>: {}\n".format(len(grp.acts)))
+            for idx, act in enumerate(grp.acts):
+                self.add_info("  {}) <u>on {} {} 0x{:X} 0x{:X}</u>, dlgs: "\
+                    "{} / {}\n".format(idx, fmt_opcode(act.opcode), 
+                        self.fmt_hl_obj(act.ref), act.arg1, act.arg2, \
+                        len(act.dlgs), self.fmt_hl_obj(act.ref, True)))
+                for didx, dlg in enumerate(act.dlgs):
                     self.add_info("    {}) <i>0x{:X} 0x{:X}</i>, ops: {}\n".\
                         format(didx, dlg.arg1, dlg.arg2, len(dlg.ops)))
                     for op in dlg.ops:
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 7d6b07643..d0ae3c49f 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -85,9 +85,9 @@ class MsgObject:
         self.capt = None
 
 class DlgGrpObject:
-    def __init__(self, idx, num_sets, arg1):
+    def __init__(self, idx, num_acts, arg1):
         self.idx = idx
-        self.num_sets = num_sets
+        self.num_acts = num_acts
         self.arg1 = arg1
         self.acts = None
 
@@ -99,6 +99,7 @@ class DlgActObject:
         self.arg1 = arg1
         self.arg2 = arg2
         self.dlgs = None
+        self.obj = None
 
 class DlgObject:
     def __init__(self, op_start, arg1, arg2):
@@ -378,21 +379,25 @@ class Engine:
                 num_grps = struct.unpack_from("<I", temp)[0]
                 for i in range(num_grps):
                     temp = f.read(12)
-                    idx, num_sets, arg1 = struct.unpack_from("<III", temp)
-                    grp = DlgGrpObject(idx, num_sets, arg1)
+                    idx, num_acts, arg1 = struct.unpack_from("<III", temp)
+                    grp = DlgGrpObject(idx, num_acts, arg1)
                     self.dlgs.append(grp)
                 opref = {}
                 for grp in self.dlgs:
-                    grp.sets = []
-                    for i in range(grp.num_sets):
+                    grp.acts = []
+                    for i in range(grp.num_acts):
                         temp = f.read(16)
-                        arg1, num_dlgs, arg2, arg3 = \
-                            struct.unpack_from("<4I", temp)
-                        dlgset = DlgSetObject(num_dlgs, arg1, arg2, arg3)
-                        grp.sets.append(dlgset)
-                    for dlgset in grp.sets:
-                        dlgset.dlgs = []
-                        for i in range(dlgset.num_dlgs):
+                        opcode, ref, num_dlgs, arg1, arg2 = \
+                            struct.unpack_from("<2H3I", temp)
+                        act = DlgActObject(num_dlgs, opcode, ref, arg1, arg2)
+                        if ref not in self.obj_idx:
+                            raise EngineError("Dialog group 0x{:x} refered "\
+                                "to unexisted object 0x{:x}".format(grp.idx, ref))
+                        act.obj = self.obj_idx[act.ref]
+                        grp.acts.append(act)
+                    for act in grp.acts:
+                        act.dlgs = []
+                        for i in range(act.num_dlgs):
                             temp = f.read(12)
                             op_start, arg1, arg2 = \
                                 struct.unpack_from("<3I", temp)
@@ -402,7 +407,7 @@ class Engine:
                             dlg = DlgObject(op_start, arg1, arg2)
                             opref[op_start] = dlg
                             dlg.ops = None
-                            dlgset.dlgs.append(dlg)
+                            act.dlgs.append(dlg)
                 temp = f.read(4)
                 num_ops = struct.unpack_from("<I", temp)[0]
                 for i in range(num_ops):


Commit: f6dc6ed300fdb606e79f25e7a51c9f14507e0339
    https://github.com/scummvm/scummvm-tools/commit/f6dc6ed300fdb606e79f25e7a51c9f14507e0339
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: refactor

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 64a1fb432..00ff0ef5d 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -555,12 +555,12 @@ class App(tkinter.Frame):
         return self.fmt_hl_rec(self.sim.objects, "objs", obj_idx, full)
         
     def fmt_hl_scene(self, scn_idx, full = False):
-        return self.fmt_hl_rec(self.sim.scenes, "scene", scn_idx, full)
+        return self.fmt_hl_rec(self.sim.scenes, "scenes", scn_idx, full)
 
     def fmt_hl_obj_scene(self, rec_idx, full = False):
         if rec_idx in self.sim.obj_idx:
-            return self.fmt_hl_rec(self.sim.objects, "objs", obj_idx, full)
-        return self.fmt_hl_rec(self.sim.scenes, "scene", scn_idx, full)
+            return self.fmt_hl_rec(self.sim.objects, "objs", rec_idx, full)
+        return self.fmt_hl_rec(self.sim.scenes, "scenes", rec_idx, full)
         
     def find_path_name(self, key):
         for idx, name in enumerate(self.sim.namesord):
@@ -580,6 +580,9 @@ class App(tkinter.Frame):
                 return "/dlgs/{}".format(idx)
         return "/no_dlgs/{}".format(grp_idx)
 
+    def fmt_hl_msg(self, obj_idx, full = False):
+        return self.fmt_hl_rec(self.sim.msgs, "msgs", obj_idx, full)
+
     def path_info_outline(self):
         self.add_info("Current part {} chapter {}\n\n".\
                 format(self.sim.curr_part, self.sim.curr_chap))
@@ -732,9 +735,8 @@ class App(tkinter.Frame):
                         if ru: break
                         for op_id, op_code, op_res, op4, op5 in ops:
                             if res_id == op_res:
-                                self.add_info("  <a href=\"/{}/{}\">{}</a> "\
-                                    "(0x{:X}) - {}\n".format(tp, idx, rec.idx, \
-                                        rec.idx, hlesc(rec.name)))
+                                self.add_info("  " + 
+                                    self.fmt_hl_obj_scene(rec.idx, True) + "\n")
                                 ru = True
                                 break
                             #print(op_id, op_code, op_res, op4, op5)
@@ -789,7 +791,7 @@ class App(tkinter.Frame):
         ftk = list(fts.keys())
         ftk.sort()
         for ft in ftk:
-            self.add_info("  <a href=\"/res/flt/{}\">{}</a>: {}\n".format(\
+            self.add_info("  <a href=\"/res/flt/{}\">{}</a>: {}\n".format(
                 ft, ft, fts[ft]))
 
     def path_res_all(self, path):
@@ -860,14 +862,13 @@ class App(tkinter.Frame):
             self.add_info("  Index:  {} (0x{:X})\n  Name:   {}\n".\
                 format(rec.idx, rec.idx, hlesc(rec.name)))
             if rec.name in self.sim.names:
-                self.add_info("  <a href=\"{}\">Alias</a>:  {}\n".format(\
-                    self.find_path_name(rec.name), \
-                    hlesc(self.sim.names[rec.name])))
+                self.add_info("  " + fmt_hl(self.find_path_name(rec.name), 
+                    "Alias") + ":  {}\n".format(
+                        hlesc(self.sim.names[rec.name])))
             if rec.name in self.sim.invntr:
-                self.add_info("  <a href=\"{}\">Invntr</a>: {}\n".format(\
-                    self.find_path_invntr(rec.name), \
-                    hlesc(self.sim.invntr[rec.name])))
-
+                self.add_info("  " + fmt_hl(self.find_path_name(rec.name), 
+                    "Invntr") + ": {}\n".format(
+                        hlesc(self.sim.invntr[rec.name])))
             # references / backreferences                    
             if isobj:
                 # search where object used
@@ -875,9 +876,8 @@ class App(tkinter.Frame):
                 for scn in self.sim.scenes:
                     for ref in scn.refs:
                         if ref[0].idx == rec.idx:
-                            self.add_info("  <a href=\"{}\">{}</a> (0x{:X}) "\
-                                "- {}\n".format(self.find_path_scene(scn.idx), \
-                                scn.idx, scn.idx, scn.name))
+                            self.add_info("  " + 
+                                self.fmt_hl_scene(scn.idx, True) + "\n")
                             break
             else:
                 if len(rec.refs) == 0:
@@ -886,8 +886,8 @@ class App(tkinter.Frame):
                     self.add_info("\n<b>References</b>: {}\n".\
                         format(len(rec.refs)))
                 for idx, ref in enumerate(rec.refs):
-                    self.add_info("  {}) <a href=\"{}\">{}</a>".format(idx,\
-                        self.find_path_obj(ref[0].idx), ref[0].idx))
+                    self.add_info("  {}) ".format(idx) + 
+                        self.fmt_hl_obj(ref[0].idx))
                     msg = ""
                     for arg in ref[1:]:
                         msg += " "
@@ -940,8 +940,7 @@ class App(tkinter.Frame):
                 self.add_info("\n<b>Messages</b>:\n")
                 for msg in self.sim.msgs:
                     if msg.obj.idx != rec.idx: continue
-                    self.add_info("  <a href=\"/msgs/{}\">{}</a> (0x{:X}) - {}\n".\
-                        format(msg.idx, msg.idx, msg.idx, hlesc(msg.capt)))
+                    self.add_info("  " + self.fmt_hl_msg(msg.idx, True) + "\n")
 
     def path_names(self, path):
         self.switch_view(0)
@@ -967,10 +966,7 @@ class App(tkinter.Frame):
             self.add_info("<b>Applied for</b>:\n")
             for idx, obj in enumerate(self.sim.objects):
                 if obj.name == name:
-                    self.add_info("  <a href=\"/objs/{}\">{}</a> (0x{:X}) "\
-                        "- {}\n".format(idx, obj.idx, obj.idx, \
-                        hlesc(obj.name)))
-
+                    self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
     def path_invntr(self, path):
         self.switch_view(0)
         if self.last_path[:1] != ("invntr",):
@@ -995,16 +991,14 @@ class App(tkinter.Frame):
             self.add_info("<b>Applied for</b>:\n")
             for idx, obj in enumerate(self.sim.objects):
                 if obj.name == name:
-                    self.add_info("  <a href=\"/objs/{}\">{}</a> (0x{:X}) "\
-                        "- {}\n".format(idx, obj.idx, obj.idx,
-                        hlesc(obj.name)))
+                    self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
 
     def path_msgs(self, path):
         self.switch_view(0)
         if self.last_path[:1] != ("msgs",):
             self.update_gui("Messages ({})".format(len(self.sim.msgs)))
             for idx, msg in enumerate(self.sim.msgs):
-                capt = msg.capt
+                capt = msg.name
                 if len(capt) > 25:
                     capt = capt[:25] + "|"
                 self.insert_lb_act("{} - {}".format(msg.idx, capt),
@@ -1028,12 +1022,12 @@ class App(tkinter.Frame):
                     msg.obj.name))
             self.add_info("  arg2:   {} (0x{:X})\n".format(msg.arg2, msg.arg2))
             self.add_info("  arg3:   {} (0x{:X})\n".format(msg.arg3, msg.arg3))
-            self.add_info("\n{}\n".format(hlesc(msg.capt)))
+            self.add_info("\n{}\n".format(hlesc(msg.name)))
 
             self.add_info("\n<b>Used by dialog groups</b>:\n")
             for grp in self.sim.dlgs:
-                for dlgset in grp.sets:
-                    for dlg in dlgset.dlgs:
+                for act in grp.acts:
+                    for dlg in act.dlgs:
                         for op in dlg.ops:
                             if not op.msg: continue
                             if op.msg.idx == msg.idx and op.opcode == 7:
@@ -1083,11 +1077,9 @@ class App(tkinter.Frame):
                             if op.msg:
                                 opref = "<a href=\"/msgs/{}\">{}</a>".format(
                                     op.ref, op.ref)
-                                objref = "<a href=\"{}\">{}</a>".format(
-                                    self.find_path_obj(op.msg.obj.idx),
-                                    op.msg.obj.idx)                                
+                                objref = self.fmt_hl_obj(op.msg.obj.idx)                                
                                 cmt = " / (0x{:X}) - {}, {}".\
-                                    format(op.ref, objref, hlesc(op.msg.capt))
+                                    format(op.ref, objref, hlesc(op.msg.name))
                         self.add_info("      {} 0x{:X} {}{}\n".\
                             format(opcode, op.arg, opref, cmt))
             
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index d0ae3c49f..539944a36 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -82,7 +82,7 @@ class MsgObject:
         self.arg1 = arg1
         self.arg2 = arg2
         self.arg3 = arg3
-        self.capt = None
+        self.name = None
 
 class DlgGrpObject:
     def __init__(self, idx, num_acts, arg1):
@@ -364,7 +364,7 @@ class Engine:
                     self.msgs.append(msg)
                 for i, capt in enumerate(f.read().split(b"\x00")):
                     if i < len(self.msgs):
-                        self.msgs[i].capt = capt.decode(self.enc)
+                        self.msgs[i].name = capt.decode(self.enc)
             finally:
                 f.close()
 


Commit: ace762238255ee93e0b74c0014d76833617714dc
    https://github.com/scummvm/scummvm-tools/commit/ace762238255ee93e0b74c0014d76833617714dc
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: more links to dialogs

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 00ff0ef5d..d64404a18 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -21,6 +21,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
+VERSION = "v0.2 2014-05-15"
 
 def hlesc(value):
     return value.replace("\\", "\\\\").replace("<", "\\<").replace(">", "\\>")
@@ -159,12 +160,14 @@ class App(tkinter.Frame):
         self.path_handler["msgs"] = self.path_msgs
         self.path_handler["dlgs"] = self.path_dlgs
         self.path_handler["test"] = self.path_test
+        self.path_handler["about"] = self.path_about
         
         self.update_after()
-        #self.open_path("")
+        self.open_path("/about")
         #self.open_path(self.find_path_scene(36))
         #self.open_path(["res", "flt", "BMP", 7])
-        self.open_path(["dlgs", 6])
+        #self.open_path(["dlgs", 6])
+        #self.open_path(self.find_path_obj(448))
 
     def create_menu(self):
         self.menubar = tkinter.Menu(self.master)
@@ -214,6 +217,13 @@ class App(tkinter.Frame):
                 command = lambda: self.open_path("/dlgs"),
                 label = "Dialog groups")
 
+        self.menuhelp = tkinter.Menu(self.master, tearoff = 0)
+        self.menubar.add_cascade(menu = self.menuhelp,
+                label = "Help")
+        self.menuhelp.add_command(
+                command = lambda: self.open_path("/about"),
+                label = "About")
+
     def update_after(self):
         if not self.need_update:
             self.after_idle(self.on_idle)
@@ -547,7 +557,10 @@ class App(tkinter.Frame):
             if rec.idx == rec_idx:
                 fmt = fmt_hl("/{}/{}".format(pref, idx), str(rec_idx))
                 if full:
-                    fmt += " (0x{:X}) - {}".format(rec.idx, hlesc(rec.name))
+                    try:
+                        fmt += " (0x{:X}) - {}".format(rec.idx, hlesc(rec.name))
+                    except:
+                        fmt += " (0x{:X})".format(rec.idx)
                 return fmt
         return "{} (0x{:X})".format(rec_idx, rec_idx)
         
@@ -583,6 +596,9 @@ class App(tkinter.Frame):
     def fmt_hl_msg(self, obj_idx, full = False):
         return self.fmt_hl_rec(self.sim.msgs, "msgs", obj_idx, full)
 
+    def fmt_hl_dlg(self, grp_idx, full = False):
+        return self.fmt_hl_rec(self.sim.dlgs, "dlgs", grp_idx, full)
+
     def path_info_outline(self):
         self.add_info("Current part {} chapter {}\n\n".\
                 format(self.sim.curr_part, self.sim.curr_chap))
@@ -676,8 +692,7 @@ class App(tkinter.Frame):
             self.clear_info()
             self.add_info("<b>Resource</b>: {} (0x{:X}) - \"{}\"\n\n".\
                 format(res_id, res_id, hlesc(fn)))
-            self.add_info("<a href=\"{}/view\">View</a> "\
-                "<a href=\"{}/used\">Used by</a>\n\n".\
+            self.add_info("<a href=\"{}/view\">View</a>\n\n".\
                 format(resref, resref))
             try:
                 if fn[-4:].lower() == ".bmp":
@@ -716,19 +731,13 @@ class App(tkinter.Frame):
                                 flc.frame_num, flc.delay))
                 else:
                     self.add_info("No information availiable")
+
+
             except:
                 self.add_info("Error loading {} - \"{}\" \n\n{}".\
                     format(res_id, hlesc(fn), hlesc(traceback.format_exc())))
-                    
-        elif mode[0] == "view":
-            self.path_res_view(res_id)
-        elif mode[0] == "used":
-            self.switch_view(0)
-            fn = self.sim.res[res_id]
-            self.clear_info()
-            self.add_info("<b>Resource</b>: <a href=\"{}\">{}</a> (0x{:X}) "\
-                "- \"{}\"\n\n".format(resref, res_id, res_id, hlesc(fn)))
-            def usedby(lst, tp):
+
+            def usedby(lst):
                 for idx, rec in enumerate(lst):
                     ru = False
                     for act_id, act_cond, act_arg, ops in rec.acts:
@@ -740,10 +749,14 @@ class App(tkinter.Frame):
                                 ru = True
                                 break
                             #print(op_id, op_code, op_res, op4, op5)
-            self.add_info("<b>Used by objects</b>:\n")
-            usedby(self.sim.objects, "objs")
+
+            self.add_info("\n\n<b>Used by objects</b>:\n")
+            usedby(self.sim.objects)
             self.add_info("\n<b>Used by scenes</b>:\n")
-            usedby(self.sim.scenes, "scenes")
+            usedby(self.sim.scenes)
+                    
+        elif mode[0] == "view":
+            self.path_res_view(res_id)
                 
     def path_res_view(self, res_id):
         fn = self.sim.res[res_id]
@@ -900,6 +913,7 @@ class App(tkinter.Frame):
                     self.add_info(msg + " / {}\n".format(hlesc(ref[0].name)))
 
             resused = []
+            dlgused = []
             self.add_info("\n<b>Handlers</b>: {}\n".format(len(rec.acts)))
             for idx, (act_id, act_cond, act_arg, ops) in enumerate(rec.acts):
                 msg = fmt_opcode(act_id)
@@ -909,11 +923,12 @@ class App(tkinter.Frame):
                     idx, msg, len(ops)))
                 for oidx, op in enumerate(ops):
                     self.add_info("    {}) {} ".format(oidx, fmt_opcode(op[1])))
+                    cmt = ""
                     if op[0] == rec.idx:
                         self.add_info("THIS")
                     else:
-                        self.add_info("<a href=\"{}\">{}</a>".format(\
-                            self.find_path_obj_scene(op[0]), op[0]))
+                        self.add_info(self.fmt_hl_obj_scene(op[0]))
+                        cmt = " / " + self.fmt_hl_obj_scene(op[0], True)
                     msg = ""
                     if op[2] != 0xffff:
                         if op[2] not in resused and op[2] in self.sim.res:
@@ -926,7 +941,10 @@ class App(tkinter.Frame):
                             msg += "-1"
                         else:
                             msg += "0x{:X}".format(arg)
-                    self.add_info("{}\n".format(msg))
+                    self.add_info("{}{}\n".format(msg, cmt))
+                    if op[1] == 0x11: # DIALOG
+                        if op[0] not in dlgused:
+                            dlgused.append(op[0])
                     
             if len(resused) > 0:
                 self.add_info("\n<b>Used resources</b>: {}\n".\
@@ -935,6 +953,12 @@ class App(tkinter.Frame):
                     self.add_info("  <a href=\"{}\">{}</a> (0x{:X}) - {}\n".\
                         format(self.find_path_res(res_id), res_id, res_id, \
                         hlesc(self.sim.res[res_id])))
+
+            if len(dlgused) > 0:
+                self.add_info("\n<b>Used dialog groups</b>: {}\n".\
+                    format(len(dlgused)))
+                for grp_id in dlgused:
+                    self.add_info("  " + self.fmt_hl_dlg(grp_id, True)+ "\n")
             
             if isobj:
                 self.add_info("\n<b>Messages</b>:\n")
@@ -967,6 +991,7 @@ class App(tkinter.Frame):
             for idx, obj in enumerate(self.sim.objects):
                 if obj.name == name:
                     self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
+                    
     def path_invntr(self, path):
         self.switch_view(0)
         if self.last_path[:1] != ("invntr",):
@@ -1031,11 +1056,8 @@ class App(tkinter.Frame):
                         for op in dlg.ops:
                             if not op.msg: continue
                             if op.msg.idx == msg.idx and op.opcode == 7:
-                                self.add_info("  <a href=\"{}\">{}</a>\n".\
-                                    format(self.find_path_dlggrp(grp.idx), 
-                                        grp.idx))
-                                
-                
+                                self.add_info("  " + 
+                                    self.fmt_hl_dlg(grp.idx, True) + "\n")
                 
     def path_dlgs(self, path):
         self.switch_view(0)
@@ -1070,18 +1092,36 @@ class App(tkinter.Frame):
                         format(didx, dlg.arg1, dlg.arg2, len(dlg.ops)))
                     for op in dlg.ops:
                         cmt = ""
-                        opref = "0x{:X}".format(op.ref)
-                        opcode = "OP_{:02X}".format(op.opcode)
+                        msgref = "0x{:X}".format(op.ref)
+                        opcode = "OP{:02X}".format(op.opcode)
                         if op.opcode == 7:
                             opcode = "PLAY"
                             if op.msg:
-                                opref = "<a href=\"/msgs/{}\">{}</a>".format(
-                                    op.ref, op.ref)
-                                objref = self.fmt_hl_obj(op.msg.obj.idx)                                
-                                cmt = " / (0x{:X}) - {}, {}".\
-                                    format(op.ref, objref, hlesc(op.msg.name))
+                                msgref = self.fmt_hl_msg(op.ref)
+                                objref = self.fmt_hl_obj(op.msg.obj.idx)
+                                cmt = " / obj={}, msg={}".\
+                                    format(objref, 
+                                        self.fmt_hl_msg(op.ref, True))
                         self.add_info("      {} 0x{:X} {}{}\n".\
-                            format(opcode, op.arg, opref, cmt))
+                            format(opcode, op.arg, msgref, cmt))
+
+            def usedby(lst):
+                for idx, rec in enumerate(lst):
+                    ru = False
+                    for act_id, act_cond, act_arg, ops in rec.acts:
+                        if ru: break
+                        for op_id, op_code, op_res, op4, op5 in ops:
+                            if op_code == 0x11 and op_id == grp.idx: # DIALOG 
+                                self.add_info("  " + 
+                                    self.fmt_hl_obj_scene(rec.idx, True) + "\n")
+                                ru = True
+                                break
+                            #print(op_id, op_code, op_res, op4, op5)
+
+            self.add_info("\n\n<b>Used by objects</b>:\n")
+            usedby(self.sim.objects)
+            self.add_info("\n<b>Used by scenes</b>:\n")
+            usedby(self.sim.scenes)
             
 
     def path_test(self, path):
@@ -1100,6 +1140,19 @@ class App(tkinter.Frame):
             for i in range(100):
                 self.add_info("  Item {}\n".format(i))
 
+    def path_about(self, path):
+        self.switch_view(0)
+        self.update_gui("About")
+        self.insert_lb_act("Outline", [])
+        self.clear_info()
+        self.add_info("Welcome to <b>Petka 1 & 2 resource explorer</b>\n\n")
+        self.add_info("  " + APPNAME + " " + VERSION + "\n")
+        self.add_info("  romiq.kh at gmail.com\n")
+        self.add_info("  https://bitbucket.org/romiq/p12simtran\n")
+        self.add_info("\n")
+        self.path_info_outline()
+
+
     def on_open_data(self):
         # open data - select TODO
         pass


Commit: 720c836c5d5d109af8ccb8e08da182d346af6d85
    https://github.com/scummvm/scummvm-tools/commit/720c836c5d5d109af8ccb8e08da182d346af6d85
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: fix underline on win

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index d64404a18..74373f427 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -47,9 +47,9 @@ class HyperlinkManager:
         italic_font = font.Font(text, self.text.cget("font"))
         italic_font.configure(slant = "italic")
         self.text.tag_config("italic", font = italic_font)
-        underline_font = font.Font(text, self.text.cget("font"))
-        underline_font.configure(underline = 1)
-        self.text.tag_config("underline", font = underline_font)
+        #underline_font = font.Font(text, self.text.cget("font"))
+        #underline_font.configure(underline = 1)
+        self.text.tag_config("underline", underline = 1)
         self.reset()
 
     def reset(self):


Commit: b3e0b4b37b5d6e5fd83dfbfb910bf4ee2e1fec5e
    https://github.com/scummvm/scummvm-tools/commit/b3e0b4b37b5d6e5fd83dfbfb910bf4ee2e1fec5e
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: History (only back)

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 74373f427..6683b871f 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -47,8 +47,6 @@ class HyperlinkManager:
         italic_font = font.Font(text, self.text.cget("font"))
         italic_font.configure(slant = "italic")
         self.text.tag_config("italic", font = italic_font)
-        #underline_font = font.Font(text, self.text.cget("font"))
-        #underline_font.configure(underline = 1)
         self.text.tag_config("underline", underline = 1)
         self.reset()
 
@@ -97,10 +95,13 @@ class App(tkinter.Frame):
         self.curr_main = -1 # 0 - frame, 1 - canvas
         self.curr_path = []
         self.last_path = [None]
+        self.last_capt = ""
         self.curr_mode = 0
         self.curr_mode_sub = None
         self.curr_gui = []
         self.curr_lb_acts = None
+        self.hist = []
+        self.histf = []
         # canvas
         self.need_update = False
         self.canv_view_fact = 1
@@ -113,8 +114,21 @@ class App(tkinter.Frame):
         ttk.Style().configure("TLabel", padding = self.pad)
         ttk.Style().configure('Info.TFrame', background = 'white', \
             foreground = "black")
+
+        # toolbar
+        self.toolbar = ttk.Frame(self)
+        self.toolbar.pack(fill = tkinter.BOTH)
+        btns = [
+            ["Outline", lambda: self.open_path("")],
+            ["<-", self.on_back],
+            ["->", self.on_forward],
+        ]
+        for text, cmd in btns:
+            btn = ttk.Button(self.toolbar, text = text, \
+                style = "Tool.TButton", command = cmd)
+            btn.pack(side = tkinter.LEFT)
         
-        # main paned
+        # main panel
         self.pan_main = ttk.PanedWindow(self, orient = tkinter.HORIZONTAL)
         self.pan_main.pack(fill = tkinter.BOTH, expand = 1)
         
@@ -187,10 +201,6 @@ class App(tkinter.Frame):
         self.menuedit = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menuedit,
                 label = "Edit")
-        self.menuedit.add_command(
-                command = lambda: self.open_path([]),
-                label = "Outline")
-        self.menuedit.add_separator()
         self.menuedit.add_command(
                 command = lambda: self.open_path("/parts"),
                 label = "Select part")
@@ -217,6 +227,24 @@ class App(tkinter.Frame):
                 command = lambda: self.open_path("/dlgs"),
                 label = "Dialog groups")
 
+        self.menunav = tkinter.Menu(self.master, tearoff = 0)
+        self.menubar.add_cascade(menu = self.menunav,
+                label = "Navigation")
+        self.menunav.add_command(
+                command = self.on_back,
+                label = "Back")
+        self.menunav.add_command(
+                command = self.on_forward,
+                label = "Forward")
+        self.menunav.add_separator()
+        self.menunav.add_command(
+                command = lambda: self.open_path(""),
+                label = "Outline")
+        self.menunav.add_separator()
+        self.menunav.add_command(
+                command = lambda: self.open_path("/hist"),
+                label = "History")
+
         self.menuhelp = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menuhelp,
                 label = "Help")
@@ -251,7 +279,7 @@ class App(tkinter.Frame):
     def on_resize_view(self, event):
         self.update_after()
  
-    def open_path(self, loc):
+    def open_path(self, loc, withhist = True):
         if isinstance(loc, str):
             path = []
             if loc[:1] == "/":
@@ -265,6 +293,12 @@ class App(tkinter.Frame):
         else:
             path = loc
         path = tuple(path)
+
+        if withhist:
+            self.hist.append([path])
+            self.histf = []
+            print(self.hist)
+
         print("DEBUG: Open", path)
         self.curr_path = path
         if len(path) > 0:
@@ -525,6 +559,19 @@ class App(tkinter.Frame):
             if act[1] is not None:
                 self.open_path(act[1])
 
+    def on_back(self):
+        print("BACK", self.hist)
+        if len(self.hist) > 1:
+            np = self.hist[-2:-1][0]
+            print(np[0])
+            self.hist = self.hist[:-1]
+            self.histf = [np] + self.histf
+            self.open_path(np[0], False)
+        
+
+    def on_forward(self):
+        print("FORWARD")
+
     def find_path_res(self, res):
         for idx, res_id in enumerate(self.sim.resord):
             if res_id == res:


Commit: 0430f3a412b172b168a21880967ea4227253238d
    https://github.com/scummvm/scummvm-tools/commit/0430f3a412b172b168a21880967ea4227253238d
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: History (only back)

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 6683b871f..714905a10 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -535,7 +535,7 @@ class App(tkinter.Frame):
 
     def select_lb_item(self, idx):
         idx = "{}".format(idx)
-        need = True
+        need = (idx is not None)
         for sel in self.curr_lb.curselection():
             if sel == idx:
                 need = False
@@ -543,7 +543,8 @@ class App(tkinter.Frame):
                 self.curr_lb.selection_clear(sel)
         if need:
             self.curr_lb.selection_set(idx)
-        self.curr_lb.see(idx)
+        if idx is not None:
+            self.curr_lb.see(idx)
             
     def on_left_listbox(self, event):
         def currsel():
@@ -713,6 +714,8 @@ class App(tkinter.Frame):
             else:
                 cnum = 0
             self.sim.open_part(pnum, cnum)
+        else:
+            self.select_lb_item(None)
         # display
         self.clear_info()
         self.add_info("Select <b>part</b>\n\n")
@@ -868,6 +871,7 @@ class App(tkinter.Frame):
             self.path_res_open(path[:3], res_id, path[3:])
         else:
             self.path_res_status()
+            self.select_lb_item(None)
 
     def path_res_flt(self, path):
         lst = []
@@ -889,6 +893,7 @@ class App(tkinter.Frame):
             self.path_res_open(path[:4], res_id, path[4:])
         else:
             self.path_res_status()
+            self.select_lb_item(None)
 
     def path_objs_scenes(self, path):
         self.switch_view(0)
@@ -911,6 +916,8 @@ class App(tkinter.Frame):
             # index
             self.select_lb_item(path[1])
             rec = lst[path[1]]
+        else:
+            self.select_lb_item(None)
         # display
         self.clear_info()
         if not rec:
@@ -1025,6 +1032,8 @@ class App(tkinter.Frame):
             # parts
             self.select_lb_item(path[1])
             name = self.sim.namesord[path[1]]
+        else:
+            self.select_lb_item(None)
         # display
         self.clear_info()
         if not name:
@@ -1081,6 +1090,8 @@ class App(tkinter.Frame):
             # parts
             self.select_lb_item(path[1])
             msg = self.sim.msgs[path[1]]
+        else:
+            self.select_lb_item(None)
         # display
         self.clear_info()
         if not msg:
@@ -1119,6 +1130,8 @@ class App(tkinter.Frame):
             # parts
             self.select_lb_item(path[1])
             grp = self.sim.dlgs[path[1]]
+        else:
+            self.select_lb_item(None)
         # display
         self.clear_info()
         if not grp:


Commit: 0e69814055093fc0ec885ef5e26e2058e0786403
    https://github.com/scummvm/scummvm-tools/commit/0e69814055093fc0ec885ef5e26e2058e0786403
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: History fixed for not item selected

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 714905a10..d7240112a 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -534,17 +534,17 @@ class App(tkinter.Frame):
         self.curr_lb.insert(tkinter.END, name)
 
     def select_lb_item(self, idx):
-        idx = "{}".format(idx)
         need = (idx is not None)
+        idxs = "{}".format(idx)
         for sel in self.curr_lb.curselection():
-            if sel == idx:
+            if sel == idxs:
                 need = False
             else:
                 self.curr_lb.selection_clear(sel)
         if need:
-            self.curr_lb.selection_set(idx)
+            self.curr_lb.selection_set(idxs)
         if idx is not None:
-            self.curr_lb.see(idx)
+            self.curr_lb.see(idxs)
             
     def on_left_listbox(self, event):
         def currsel():


Commit: 05a3a114030457a6db649e3d238c2f3cdbb04d53
    https://github.com/scummvm/scummvm-tools/commit/05a3a114030457a6db649e3d238c2f3cdbb04d53
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: History fixed for not item selected

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index d7240112a..271af2dda 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -561,7 +561,6 @@ class App(tkinter.Frame):
                 self.open_path(act[1])
 
     def on_back(self):
-        print("BACK", self.hist)
         if len(self.hist) > 1:
             np = self.hist[-2:-1][0]
             print(np[0])
@@ -571,7 +570,7 @@ class App(tkinter.Frame):
         
 
     def on_forward(self):
-        print("FORWARD")
+        print("FORWARD", self.histf)
 
     def find_path_res(self, res):
         for idx, res_id in enumerate(self.sim.resord):


Commit: 655ff79ed95dd02ff21009f209f0a46c2b37072d
    https://github.com/scummvm/scummvm-tools/commit/655ff79ed95dd02ff21009f209f0a46c2b37072d
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: History back and forward works

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 271af2dda..86368422d 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -297,7 +297,6 @@ class App(tkinter.Frame):
         if withhist:
             self.hist.append([path])
             self.histf = []
-            print(self.hist)
 
         print("DEBUG: Open", path)
         self.curr_path = path
@@ -563,14 +562,16 @@ class App(tkinter.Frame):
     def on_back(self):
         if len(self.hist) > 1:
             np = self.hist[-2:-1][0]
-            print(np[0])
+            self.histf = self.hist[-1:] + self.histf
             self.hist = self.hist[:-1]
-            self.histf = [np] + self.histf
             self.open_path(np[0], False)
-        
 
     def on_forward(self):
-        print("FORWARD", self.histf)
+        if len(self.histf) > 0:
+            np = self.histf[0]
+            self.histf = self.histf[1:]
+            self.hist.append(np)
+            self.open_path(np[0], False)
 
     def find_path_res(self, res):
         for idx, res_id in enumerate(self.sim.resord):


Commit: a7922be8fa34bb8784ebb0bafe577f48267ead7d
    https://github.com/scummvm/scummvm-tools/commit/a7922be8fa34bb8784ebb0bafe577f48267ead7d
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Clear history on part change

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 86368422d..2dd42ae77 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -714,6 +714,8 @@ class App(tkinter.Frame):
             else:
                 cnum = 0
             self.sim.open_part(pnum, cnum)
+            self.hist = self.hist[-1:]
+            self.histf = []
         else:
             self.select_lb_item(None)
         # display


Commit: eec272940b1ca22469298eecdec8def8c02ff8c8
    https://github.com/scummvm/scummvm-tools/commit/eec272940b1ca22469298eecdec8def8c02ff8c8
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Open dialog, small fixes

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 2dd42ae77..62b828efc 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -5,7 +5,7 @@
 
 import sys, os
 import tkinter
-from tkinter import ttk, font
+from tkinter import ttk, font, filedialog, messagebox
 from idlelib.WidgetRedirector import WidgetRedirector
 import traceback
 
@@ -21,7 +21,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2 2014-05-15"
+VERSION = "v0.2 2014-05-16"
 
 def hlesc(value):
     return value.replace("\\", "\\\\").replace("<", "\\<").replace(">", "\\>")
@@ -120,13 +120,20 @@ class App(tkinter.Frame):
         self.toolbar.pack(fill = tkinter.BOTH)
         btns = [
             ["Outline", lambda: self.open_path("")],
+            [None, None],
             ["<-", self.on_back],
             ["->", self.on_forward],
         ]
         for text, cmd in btns:
+            if text is None:
+                frm = ttk.Frame(self.toolbar, width = self.pad, height = self.pad)
+                frm.pack(side = tkinter.LEFT)
+                continue            
             btn = ttk.Button(self.toolbar, text = text, \
                 style = "Tool.TButton", command = cmd)
             btn.pack(side = tkinter.LEFT)
+        frm = ttk.Frame(self.toolbar, width = self.pad, height = self.pad)
+        frm.pack(side = tkinter.LEFT)
         
         # main panel
         self.pan_main = ttk.PanedWindow(self, orient = tkinter.HORIZONTAL)
@@ -559,6 +566,21 @@ class App(tkinter.Frame):
             if act[1] is not None:
                 self.open_path(act[1])
 
+    def add_toolbtn(self, text, cmd):
+        if text is None:
+            frm = ttk.Frame(self.toolbar, width = self.pad, height = self.pad)
+            frm.pack(side = tkinter.LEFT)
+            self.curr_gui.append(lambda:frm.pack_forget())    
+            return
+        btn = ttk.Button(self.toolbar, text = text, \
+            style = "Tool.TButton", command = cmd)
+        btn.pack(side = tkinter.LEFT)
+        self.curr_gui.append(lambda:btn.pack_forget())    
+
+    def clear_hist(self):
+        self.hist = self.hist[-1:]
+        self.histf = []
+
     def on_back(self):
         if len(self.hist) > 1:
             np = self.hist[-2:-1][0]
@@ -648,6 +670,9 @@ class App(tkinter.Frame):
         return self.fmt_hl_rec(self.sim.dlgs, "dlgs", grp_idx, full)
 
     def path_info_outline(self):
+        if self.sim is None:
+            self.add_info("No data loaded. Open BGS.INI or SCRIPT.DAT first.")
+            return
         self.add_info("Current part {} chapter {}\n\n".\
                 format(self.sim.curr_part, self.sim.curr_chap))
         self.add_info("  Resources:     <a href=\"/res\">{}</a>\n".\
@@ -714,8 +739,7 @@ class App(tkinter.Frame):
             else:
                 cnum = 0
             self.sim.open_part(pnum, cnum)
-            self.hist = self.hist[-1:]
-            self.histf = []
+            self.clear_hist()
         else:
             self.select_lb_item(None)
         # display
@@ -784,7 +808,6 @@ class App(tkinter.Frame):
                 else:
                     self.add_info("No information availiable")
 
-
             except:
                 self.add_info("Error loading {} - \"{}\" \n\n{}".\
                     format(res_id, hlesc(fn), hlesc(traceback.format_exc())))
@@ -839,8 +862,10 @@ class App(tkinter.Frame):
             self.clear_info()
             self.add_info("Error loading {} - \"{}\" \n\n{}".\
                 format(res_id, hlesc(fn), hlesc(traceback.format_exc())))
+            dataf = None
         finally:
-            dataf.close()
+            if dataf:
+                dataf.close()
 
     def path_res_status(self):
         self.switch_view(0)
@@ -858,10 +883,18 @@ class App(tkinter.Frame):
         for ft in ftk:
             self.add_info("  <a href=\"/res/flt/{}\">{}</a>: {}\n".format(
                 ft, ft, fts[ft]))
+    
+    def on_path_res_info(self):
+        self.switch_view(0)
+        
+    def on_path_res_view(self):
+        self.switch_view(1)
 
     def path_res_all(self, path):
         if self.last_path[:2] != ("res", "all",):
             self.update_gui("Resources ({})".format(len(self.sim.res)))
+            #self.add_toolbtn("Info", self.on_path_res_info)
+            #self.add_toolbtn("View", self.on_path_res_view)
             for idx, res_id in enumerate(self.sim.resord):
                     self.insert_lb_act("{} - {}".format(\
                 res_id, self.sim.res[res_id]), ["res", "all", idx])
@@ -974,7 +1007,11 @@ class App(tkinter.Frame):
             for idx, (act_id, act_cond, act_arg, ops) in enumerate(rec.acts):
                 msg = fmt_opcode(act_id)
                 if act_cond != 0xff or act_arg != 0xffff:
-                    msg += " 0x{:02X} 0x{:04X}".format(act_cond, act_arg)
+                    if act_arg == rec.idx:
+                        act_arg = "THIS"
+                    else:
+                        act_arg = "0x:{:X}".format(act_arg)
+                    msg += " 0x{:02X} {}".format(act_cond, act_arg)
                 self.add_info("  {}) <u>on {}</u>, ops: {}\n".format(\
                     idx, msg, len(ops)))
                 for oidx, op in enumerate(ops):
@@ -1216,22 +1253,41 @@ class App(tkinter.Frame):
 
 
     def on_open_data(self):
-        # open data - select TODO
-        pass
+        ft = [\
+            ('all files', '.*')]
+        fn = filedialog.askopenfilename(parent = self, 
+            title = "Open BGS.INI or SCRIPT.DAT",
+            filetypes = ft,
+            initialdir = os.path.abspath(os.curdir))
+        if not fn: return
+        os.chdir(os.path.dirname(fn))
+        self.clear_hist()
+        if self.open_data_from(os.path.dirname(fn)):
+            self.open_path("")
+            self.clear_hist()
+        
         
     def open_data_from(self, folder):
-        self.sim = petka.Engine()
-        self.sim.load_data(folder, "cp1251")
-        self.sim.open_part(1, 0)
+        try:
+            self.sim = petka.Engine()
+            self.sim.load_data(folder, "cp1251")
+            self.sim.open_part(0, 0)
+            return True
+        except:
+            print("DEBUG: Error opening")
+            self.sim = None
+            self.switch_view(0)
+            self.update_gui("")
+            self.clear_info()
+            self.add_info("Error opening \"{}\" \n\n{}".\
+                format(hlesc(folder), hlesc(traceback.format_exc())))
+            self.clear_hist()
 
 def main():
     root = tkinter.Tk()
     app = App(master = root)
     if len(sys.argv) > 1:
-        fn = sys.argv[1]
-    else:
-        fn = "."
-    app.open_data_from(fn)
+        app.open_data_from(sys.argv[1])
     app.mainloop()
 
     
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 539944a36..acc943436 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -7,6 +7,7 @@ import struct
 import io
 
 from .fman import FileManager
+from . import EngineError
 
 OPCODES = {
     1:  ("USE",         0),
@@ -259,7 +260,10 @@ class Engine:
         self.obj_idx = {}
         self.scn_idx = {}
 
-        data = self.fman.read_file(self.curr_path + "script.dat")
+        try:
+            data = self.fman.read_file(self.curr_path + "script.dat")
+        except:
+            raise EngineError("Can't open SCRIPT.DAT")
         num_obj, num_scn = struct.unpack_from("<II", data[:8])
         off = 8
         def read_rec(off):


Commit: d2c1894e9c0c60f3f21f24e3e6ab0fc412ce0755
    https://github.com/scummvm/scummvm-tools/commit/d2c1894e9c0c60f3f21f24e3e6ab0fc412ce0755
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: ref to object from action handler

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 62b828efc..23915175b 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1004,16 +1004,21 @@ class App(tkinter.Frame):
             resused = []
             dlgused = []
             self.add_info("\n<b>Handlers</b>: {}\n".format(len(rec.acts)))
-            for idx, (act_id, act_cond, act_arg, ops) in enumerate(rec.acts):
-                msg = fmt_opcode(act_id)
-                if act_cond != 0xff or act_arg != 0xffff:
-                    if act_arg == rec.idx:
-                        act_arg = "THIS"
+            for idx, (act_op, act_status, act_ref, ops) in enumerate(rec.acts):
+                msg = fmt_opcode(act_op)
+                cmt = ""
+                if act_status != 0xff or act_ref != 0xffff:
+                    if act_ref == rec.idx:
+                        act_ref = "THIS"
                     else:
-                        act_arg = "0x:{:X}".format(act_arg)
-                    msg += " 0x{:02X} {}".format(act_cond, act_arg)
-                self.add_info("  {}) <u>on {}</u>, ops: {}\n".format(\
-                    idx, msg, len(ops)))
+                        if act_ref in self.sim.obj_idx:
+                            cmt = " / " + self.fmt_hl_obj(act_ref, True)
+                            act_ref = self.fmt_hl_obj(act_ref)
+                        else:
+                            act_ref = "0x{:X}".format(act_ref)
+                    msg += " 0x{:02X} {}".format(act_status, act_ref)
+                self.add_info("  {}) <u>on {}</u>, ops: {}{}\n".format(\
+                    idx, msg, len(ops), cmt))
                 for oidx, op in enumerate(ops):
                     self.add_info("    {}) {} ".format(oidx, fmt_opcode(op[1])))
                     cmt = ""


Commit: b590021b4ce4fda0a3b882af74bcbe059f7654ff
    https://github.com/scummvm/scummvm-tools/commit/b590021b4ce4fda0a3b882af74bcbe059f7654ff
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: Dist file for cx_Freeze

Changed paths:
  A engines/petka/dist/setup.py


diff --git a/engines/petka/dist/setup.py b/engines/petka/dist/setup.py
new file mode 100644
index 000000000..729cd75b0
--- /dev/null
+++ b/engines/petka/dist/setup.py
@@ -0,0 +1,27 @@
+from cx_Freeze import setup, Executable
+
+import cx_Freeze.util
+
+import sys, os, struct
+
+# Dependencies are automatically detected, but it might need
+# fine tuning.
+buildOptions = dict(packages = ["re", "io", "PIL", "traceback", "zlib", "gzip", "argparse", "struct", "binascii"], \
+    excludes = ["_posixsubprocess"],
+    include_files = [],
+    compressed = True, silent = True,\
+    optimize = 2, copy_dependent_files = True, \
+    create_shared_zip = True, include_in_shared_zip = True)
+
+executables = [
+    Executable('p12explore.py',
+        base = 'Win32GUI',
+        targetName = "p12explore.exe")
+]
+
+setup(name='p12explore',
+      version = '0.2',
+      description = 'Petka 1&2 explorer',
+      author = "romiq.kh at gmail.com, https://bitbucket.org/romiq/p12simtran",
+      options = dict(build_exe = buildOptions),
+      executables = executables)


Commit: 726a9148be0e13d8096ccf52433ea00fb6c79a03
    https://github.com/scummvm/scummvm-tools/commit/726a9148be0e13d8096ccf52433ea00fb6c79a03
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: release 0.2a (fix using resources in freeze mode)

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/imgbmp.py
    engines/petka/petka/imgflc.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 23915175b..cbf8fd0b3 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -21,7 +21,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2 2014-05-16"
+VERSION = "v0.2a 2014-05-16"
 
 def hlesc(value):
     return value.replace("\\", "\\\\").replace("<", "\\<").replace(">", "\\>")
diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
index 631c367a0..7f3ec4c4e 100644
--- a/engines/petka/petka/imgbmp.py
+++ b/engines/petka/petka/imgbmp.py
@@ -104,8 +104,6 @@ class BMPLoader:
                 self.height = ph
                 self.rgb = self.pixelswap16(pw, ph, pd)
         except:
-            import traceback
-            traceback.print_exc()
             f.seek(0)
             self.image = Image.open(f)
             
diff --git a/engines/petka/petka/imgflc.py b/engines/petka/petka/imgflc.py
index 63040d7a8..c692db697 100644
--- a/engines/petka/petka/imgflc.py
+++ b/engines/petka/petka/imgflc.py
@@ -11,6 +11,11 @@ try:
 except ImportError:
     Image = None
 
+try:
+    from PIL import FliImagePlugin
+except ImportError:
+    pass
+
 class FLCLoader:
     def __init__(self):
         self.rgb = None


Commit: 71e35612b7f792b1db0952e0114c0cb979dab13e
    https://github.com/scummvm/scummvm-tools/commit/71e35612b7f792b1db0952e0114c0cb979dab13e
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: release 0.2b (fix errors when no data loaded, add support information)

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index cbf8fd0b3..35da3708c 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -21,7 +21,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2a 2014-05-16"
+VERSION = "v0.2b 2014-05-16"
 
 def hlesc(value):
     return value.replace("\\", "\\\\").replace("<", "\\<").replace(">", "\\>")
@@ -95,7 +95,7 @@ class App(tkinter.Frame):
         self.curr_main = -1 # 0 - frame, 1 - canvas
         self.curr_path = []
         self.last_path = [None]
-        self.last_capt = ""
+        self.last_fn = ""
         self.curr_mode = 0
         self.curr_mode_sub = None
         self.curr_gui = []
@@ -182,6 +182,7 @@ class App(tkinter.Frame):
         self.path_handler["dlgs"] = self.path_dlgs
         self.path_handler["test"] = self.path_test
         self.path_handler["about"] = self.path_about
+        self.path_handler["support"] = self.path_support
         
         self.update_after()
         self.open_path("/about")
@@ -258,6 +259,9 @@ class App(tkinter.Frame):
         self.menuhelp.add_command(
                 command = lambda: self.open_path("/about"),
                 label = "About")
+        self.menuhelp.add_command(
+                command = lambda: self.open_path("/support"),
+                label = "Support")
 
     def update_after(self):
         if not self.need_update:
@@ -700,7 +704,8 @@ class App(tkinter.Frame):
             for item in path:
                 spath += "/" + str(item)
             self.add_info("Path {} not found\n\n".format(spath))
-        self.add_info("Select from <b>outline</b>\n\n")
+        if self.sim is not None:
+            self.add_info("Select from <b>outline</b>\n\n")
         self.path_info_outline()
         if self.sim is not None:
             acts = [
@@ -720,7 +725,8 @@ class App(tkinter.Frame):
                 self.insert_lb_act(name, act)
 
     def path_parts(self, path):
-        self.switch_view(0)
+        if self.sim is None:
+            return self.path_default([])
         if self.last_path[:1] != ("parts",):
             self.update_gui("Parts ({})".format(len(self.sim.parts)))
             for idx, name in enumerate(self.sim.parts):
@@ -751,6 +757,8 @@ class App(tkinter.Frame):
         # res - full list
         # res/flt/<ext> - list by <ext>
         # res/all/<id> - display res by id
+        if self.sim is None:
+            return self.path_default([])
         if path == ("res",):
             path = ("res", "all")
         if path[1] == "flt":
@@ -931,6 +939,8 @@ class App(tkinter.Frame):
             self.select_lb_item(None)
 
     def path_objs_scenes(self, path):
+        if self.sim is None:
+            return self.path_default([])
         self.switch_view(0)
         isobj = (self.curr_path[0] == "objs")
         if isobj:
@@ -1065,6 +1075,8 @@ class App(tkinter.Frame):
                     self.add_info("  " + self.fmt_hl_msg(msg.idx, True) + "\n")
 
     def path_names(self, path):
+        if self.sim is None:
+            return self.path_default([])
         self.switch_view(0)
         if self.last_path[:1] != ("names",):
             self.update_gui("Names ({})".format(len(self.sim.names)))
@@ -1093,6 +1105,8 @@ class App(tkinter.Frame):
                     self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
                     
     def path_invntr(self, path):
+        if self.sim is None:
+            return self.path_default([])
         self.switch_view(0)
         if self.last_path[:1] != ("invntr",):
             self.update_gui("Invntr ({})".format(len(self.sim.invntr)))
@@ -1119,6 +1133,8 @@ class App(tkinter.Frame):
                     self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
 
     def path_msgs(self, path):
+        if self.sim is None:
+            return self.path_default([])
         self.switch_view(0)
         if self.last_path[:1] != ("msgs",):
             self.update_gui("Messages ({})".format(len(self.sim.msgs)))
@@ -1162,6 +1178,8 @@ class App(tkinter.Frame):
                                     self.fmt_hl_dlg(grp.idx, True) + "\n")
                 
     def path_dlgs(self, path):
+        if self.sim is None:
+            return self.path_default([])
         self.switch_view(0)
         if self.last_path[:1] != ("dlgs",):
             self.update_gui("Dialog groups ({})".format(len(self.sim.dlgs)))
@@ -1256,6 +1274,26 @@ class App(tkinter.Frame):
         self.add_info("\n")
         self.path_info_outline()
 
+    def path_support(self, path):
+        self.switch_view(0)
+        self.update_gui("Support")
+        self.insert_lb_act("Outline", [])
+        self.clear_info()
+        self.add_info("" + APPNAME + " " + VERSION + "\n")
+        self.add_info("=" * 40 + "\n")
+        self.add_info("<b>Game folder</b>: {}\n".format(hlesc(self.last_fn)))
+        if self.sim is None:
+            self.add_info("<i>Engine not initialized</i>\n")
+        else:
+            self.add_info("<i>Engine works</i>\n\n")
+            self.add_info("  <b>Path</b>:    {}\n".format(
+                hlesc(self.sim.curr_path)))
+            self.add_info("  <b>Speech</b>:  {}\n".format(
+                hlesc(self.sim.curr_speech)))
+            self.add_info("  <b>Disk ID</b>: {}\n\n".format(
+                hlesc(self.sim.curr_diskid)))
+            self.path_info_outline()
+            
 
     def on_open_data(self):
         ft = [\
@@ -1273,6 +1311,7 @@ class App(tkinter.Frame):
         
         
     def open_data_from(self, folder):
+        self.last_fn = folder
         try:
             self.sim = petka.Engine()
             self.sim.load_data(folder, "cp1251")


Commit: df731096071420ed406088fb166b734ec4a4512f
    https://github.com/scummvm/scummvm-tools/commit/df731096071420ed406088fb166b734ec4a4512f
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:14+01:00

Commit Message:
TOOLS: PETKA: release 0.2c (fix support page)

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 35da3708c..05bb13933 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -24,6 +24,8 @@ APPNAME = "P1&2 Explorer"
 VERSION = "v0.2b 2014-05-16"
 
 def hlesc(value):
+    if value is None:
+        return "None"
     return value.replace("\\", "\\\\").replace("<", "\\<").replace(">", "\\>")
 
 def fmt_opcode(opcode):


Commit: ccbd3e23f86dfcba6d687a88af56821bc9822b89
    https://github.com/scummvm/scummvm-tools/commit/ccbd3e23f86dfcba6d687a88af56821bc9822b89
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: release 0.2c

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 05bb13933..34bfe56c4 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -21,7 +21,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2b 2014-05-16"
+VERSION = "v0.2c 2014-05-16"
 
 def hlesc(value):
     if value is None:


Commit: 706c7d5b1a62a312d97eaf6a2b53fae6dafad3a9
    https://github.com/scummvm/scummvm-tools/commit/706c7d5b1a62a312d97eaf6a2b53fae6dafad3a9
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Fix gui, wat to fix path with incorrect loading

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/fman.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 34bfe56c4..f8fe445f0 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -21,7 +21,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2c 2014-05-16"
+VERSION = "v0.2d 2014-05-16"
 
 def hlesc(value):
     if value is None:
@@ -444,7 +444,9 @@ class App(tkinter.Frame):
         scr_lb_x.grid(row = 1, column = 0, sticky = tkinter.E + tkinter.W)
         scr_lb_y = ttk.Scrollbar(frm_lb)
         scr_lb_y.grid(row = 0, column = 1, sticky = tkinter.N + tkinter.S)
+        frmlbpad = ttk.Frame(frm_lb, borderwidth = self.pad)
         lb = tkinter.Listbox(frm_lb,
+            highlightthickness = 0,
             xscrollcommand = scr_lb_x.set,
             yscrollcommand = scr_lb_y.set)
         lb.grid(row = 0, column = 0, \
@@ -543,7 +545,10 @@ class App(tkinter.Frame):
         
     def insert_lb_act(self, name, act):
         self.curr_lb_acts.append((name, act))
-        self.curr_lb.insert(tkinter.END, name)
+        if name == "-" and act is None:
+            self.curr_lb.insert(tkinter.END, "")
+        else:
+            self.curr_lb.insert(tkinter.END, " " + name)
 
     def select_lb_item(self, idx):
         need = (idx is not None)
@@ -677,7 +682,7 @@ class App(tkinter.Frame):
 
     def path_info_outline(self):
         if self.sim is None:
-            self.add_info("No data loaded. Open BGS.INI or SCRIPT.DAT first.")
+            self.add_info("No data loaded. Open PARTS.INI or SCRIPT.DAT first.")
             return
         self.add_info("Current part {} chapter {}\n\n".\
                 format(self.sim.curr_part, self.sim.curr_chap))
@@ -726,6 +731,7 @@ class App(tkinter.Frame):
             for name, act in acts:
                 self.insert_lb_act(name, act)
 
+
     def path_parts(self, path):
         if self.sim is None:
             return self.path_default([])
@@ -733,6 +739,19 @@ class App(tkinter.Frame):
             self.update_gui("Parts ({})".format(len(self.sim.parts)))
             for idx, name in enumerate(self.sim.parts):
                 self.insert_lb_act(name, ["parts", idx])
+            if len(self.sim.parts) == 0:
+                # Option to fix paths
+                def fix_paths():
+                    self.sim.curr_path = ""
+                    path = self.sim.fman.root
+                    while self.sim.curr_path == "":
+                       self.sim.curr_path = os.path.basename(path)
+                       path2 = os.path.dirname(path)
+                       if path2 == path: break
+                       path = path2
+                    path = self.sim.fman.root = path
+                    self.sim.curr_path += "\\"
+                self.add_toolbtn("Fix paths", fix_paths)
         # change                
         if len(path) > 1:
             # parts
@@ -1283,7 +1302,8 @@ class App(tkinter.Frame):
         self.clear_info()
         self.add_info("" + APPNAME + " " + VERSION + "\n")
         self.add_info("=" * 40 + "\n")
-        self.add_info("<b>Game folder</b>: {}\n".format(hlesc(self.last_fn)))
+        self.add_info("<b>Game folder</b>: {}\n".format(
+            hlesc(self.sim.fman.root)))
         if self.sim is None:
             self.add_info("<i>Engine not initialized</i>\n")
         else:
@@ -1301,7 +1321,7 @@ class App(tkinter.Frame):
         ft = [\
             ('all files', '.*')]
         fn = filedialog.askopenfilename(parent = self, 
-            title = "Open BGS.INI or SCRIPT.DAT",
+            title = "Open PARTS.INI or SCRIPT.DAT",
             filetypes = ft,
             initialdir = os.path.abspath(os.curdir))
         if not fn: return
diff --git a/engines/petka/petka/fman.py b/engines/petka/petka/fman.py
index acb5c362b..e9f9dea76 100644
--- a/engines/petka/petka/fman.py
+++ b/engines/petka/petka/fman.py
@@ -11,7 +11,7 @@ from . import EngineError
 # manage files data
 class FileManager:
     def __init__(self, root):
-        self.root = root
+        self.root = os.path.abspath(root)
     
         self.strfd = []
         self.strtable = {}


Commit: 186095934c485810babfc0eeffde8a44af7e3840
    https://github.com/scummvm/scummvm-tools/commit/186095934c485810babfc0eeffde8a44af7e3840
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Help pages

Changed paths:
  A engines/petka/help/changes.txt
  A engines/petka/help/faq.txt
  A engines/petka/help/index.txt
  A engines/petka/help/list
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
new file mode 100644
index 000000000..73a82330d
--- /dev/null
+++ b/engines/petka/help/changes.txt
@@ -0,0 +1,31 @@
+Что нового
+==========
+
+2014-05-16 версия 0.2e
+----------------------
+Исправления на странице Support
+Добавлена справка по программе
+
+2014-05-16 версия 0.2d
+----------------------
+Исправления на странице Support
+Добавлена кнопка "Fix Paths" - исправление путей когда загружена только одна 
+  часть
+
+2014-05-16 версия 0.2c
+----------------------
+Исправления на странице Support
+
+2014-05-16 версия 0.2b
+----------------------
+Исправлено открытие разделов когда данные не загружены
+Добавлена страница Support
+
+2014-05-16 версия 0.2a
+----------------------
+Исправлено открытие ресурсов при использовании cx_Freeze
+
+2014-05-16 версия 0.2
+---------------------
+Первый публичный релиз
+
diff --git a/engines/petka/help/faq.txt b/engines/petka/help/faq.txt
new file mode 100644
index 000000000..1e30f7fe7
--- /dev/null
+++ b/engines/petka/help/faq.txt
@@ -0,0 +1,9 @@
+Часто задаваемые вопросы (FAQ)
+
+<i>Я успешно открыл файл SCRIPT.DAT, но ни один из ресурсов не загружается</i>
+1. Проверьте пути к ресурсам
+2. Возможно, вы открыли файл из каталога PART_0 (PART_1 и т.д.) вместо 
+  PARTS.INI из корневого каталога. Функция открытия файла SCRIPT.DAT 
+  предназначена для Демо-версии 1й части. Тем не менее вы можете перейти в 
+  раздел выбор части (Edit->Parts) и нажать кнопку [Fix Paths].
+  
diff --git a/engines/petka/help/index.txt b/engines/petka/help/index.txt
new file mode 100644
index 000000000..4a2dcad34
--- /dev/null
+++ b/engines/petka/help/index.txt
@@ -0,0 +1,7 @@
+Справка
+
+Версия: 0.2e 2014-05-16
+
+ * <a href="/help/changes">Что нового</a>
+ * <a href="/help/faq">Часто задаваемые вопросы</a>
+
diff --git a/engines/petka/help/list b/engines/petka/help/list
new file mode 100644
index 000000000..74879b370
--- /dev/null
+++ b/engines/petka/help/list
@@ -0,0 +1,4 @@
+index
+changes
+faq
+
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index f8fe445f0..6b724abd3 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -21,7 +21,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2d 2014-05-16"
+VERSION = "v0.2e 2014-05-16"
 
 def hlesc(value):
     if value is None:
@@ -92,6 +92,12 @@ class App(tkinter.Frame):
         self.pack(fill = tkinter.BOTH, expand = 1)
         self.pad = None
         self.sim = None
+        # path
+        if hasattr(sys, 'frozen'):
+            self.app_path = sys.executable
+        else:
+            self.app_path = __file__
+        self.app_path = os.path.abspath(os.path.dirname(self.app_path))
         # gui
         self.path_handler = {}
         self.curr_main = -1 # 0 - frame, 1 - canvas
@@ -185,6 +191,7 @@ class App(tkinter.Frame):
         self.path_handler["test"] = self.path_test
         self.path_handler["about"] = self.path_about
         self.path_handler["support"] = self.path_support
+        self.path_handler["help"] = self.path_help
         
         self.update_after()
         self.open_path("/about")
@@ -259,11 +266,15 @@ class App(tkinter.Frame):
         self.menubar.add_cascade(menu = self.menuhelp,
                 label = "Help")
         self.menuhelp.add_command(
-                command = lambda: self.open_path("/about"),
-                label = "About")
+                command = lambda: self.open_path("/help/index"),
+                label = "Contents")
+        self.menuhelp.add_separator()
         self.menuhelp.add_command(
                 command = lambda: self.open_path("/support"),
                 label = "Support")
+        self.menuhelp.add_command(
+                command = lambda: self.open_path("/about"),
+                label = "About")
 
     def update_after(self):
         if not self.need_update:
@@ -1302,8 +1313,10 @@ class App(tkinter.Frame):
         self.clear_info()
         self.add_info("" + APPNAME + " " + VERSION + "\n")
         self.add_info("=" * 40 + "\n")
+        self.add_info("<b>App folder</b>:  {}\n".format(
+            hlesc(self.app_path)))
         self.add_info("<b>Game folder</b>: {}\n".format(
-            hlesc(self.sim.fman.root)))
+            hlesc(self.last_fn)))
         if self.sim is None:
             self.add_info("<i>Engine not initialized</i>\n")
         else:
@@ -1316,6 +1329,64 @@ class App(tkinter.Frame):
                 hlesc(self.sim.curr_diskid)))
             self.path_info_outline()
             
+    def path_help(self, path):
+        self.switch_view(0)
+
+        lfn = os.path.join(self.app_path, "help", "list")
+        hi = []
+        f = open(lfn, "rb")
+        try:
+            for item in f.readlines():
+                item = item.decode("UTF-8").strip()
+                if not item: continue
+                try:
+                    hf = open(os.path.join(self.app_path, 
+                        "help", item + ".txt"), "rb")
+                    try:
+                        for line in hf.readlines():
+                            line = line.decode("UTF-8").strip()                    
+                            break
+                    finally:
+                        hf.close()
+                except:
+                    line = item                    
+                hi.append([item, line])
+        finally:
+            f.close()
+
+        if self.last_path[:1] != ("help",):
+            self.update_gui("Help")
+            for item, line in hi:
+                self.insert_lb_act(line, ["help", item])
+            self.insert_lb_act("-", None)
+            self.insert_lb_act("Outline", [])
+
+        # change
+        if len(path) > 1:
+            # parts
+            for idx, (item, line) in enumerate(hi):
+                if item == path[1]:
+                    self.select_lb_item(idx)
+            self.clear_info()
+            hfn = os.path.join(self.app_path, "help", path[1] + ".txt")
+            try:
+                hf = open(hfn, "rb")
+                try:
+                    for idx, line in enumerate(hf.readlines()):
+                        line = line.decode("UTF-8").rstrip()                    
+                        if idx == 0:
+                            self.add_info("<b>" + line + "</b>\n")
+                        else:                                        
+                            self.add_info(line + "\n")
+                finally:
+                    hf.close()
+            except:
+                self.add_info("Error loading \"{}\" \n\n{}".\
+                    format(hlesc(hfn), hlesc(traceback.format_exc())))
+        else:
+            self.select_lb_item(None)
+
+        
 
     def on_open_data(self):
         ft = [\


Commit: f3918517d02923a6a87d7b2a94f30a34ed056f96
    https://github.com/scummvm/scummvm-tools/commit/f3918517d02923a6a87d7b2a94f30a34ed056f96
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: refactor internal paths

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 6b724abd3..8ac6d45b3 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -108,6 +108,7 @@ class App(tkinter.Frame):
         self.curr_mode_sub = None
         self.curr_gui = []
         self.curr_lb_acts = None
+        self.curr_lb_idx = None
         self.hist = []
         self.histf = []
         # canvas
@@ -472,6 +473,7 @@ class App(tkinter.Frame):
         # actions on listbox
         self.curr_lb = lb
         self.curr_lb_acts = []
+        self.curr_lb_idx = {}
 
     def switch_view(self, main):
         # main view
@@ -550,18 +552,42 @@ class App(tkinter.Frame):
                     mode = 0
                 else:
                     curr_tag += ch
-        if len(curr_text) > 0:                    
+        if len(curr_text) > 0:                            lfn = os.path.join(self.app_path, "help", "list")
+        hi = []
+        f = open(lfn, "rb")
+        try:
+            for item in f.readlines():
+                item = item.decode("UTF-8").strip()
+                if not item: continue
+                try:
+                    hf = open(os.path.join(self.app_path, 
+                        "help", item + ".txt"), "rb")
+                    try:
+                        for line in hf.readlines():
+                            line = line.decode("UTF-8").strip()                    
+                            break
+                    finally:
+                        hf.close()
+                except:
+                    line = item                    
+                hi.append([item, line])
+        finally:
+            f.close()
+
             self.text_view.insert(tkinter.INSERT, curr_text, \
                 tuple([x for x in tags for x in x]))
         
-    def insert_lb_act(self, name, act):
+    def insert_lb_act(self, name, act, key = None):
+        if key is not None:
+            self.curr_lb_idx[key] = len(self.curr_lb_acts)
         self.curr_lb_acts.append((name, act))
         if name == "-" and act is None:
             self.curr_lb.insert(tkinter.END, "")
         else:
             self.curr_lb.insert(tkinter.END, " " + name)
 
-    def select_lb_item(self, idx):
+    def select_lb_item(self, key):
+        idx = self.curr_lb_idx.get(key, None)
         need = (idx is not None)
         idxs = "{}".format(idx)
         for sel in self.curr_lb.curselection():
@@ -617,32 +643,32 @@ class App(tkinter.Frame):
             self.hist.append(np)
             self.open_path(np[0], False)
 
-    def find_path_res(self, res):
-        for idx, res_id in enumerate(self.sim.resord):
-            if res_id == res:
-                return "/res/all/{}".format(idx)
-        return "/no_res/{}".format(res)
-
-    def find_path_obj(self, obj_idx):
-        for idx, rec in enumerate(self.sim.objects):
-            if rec.idx == obj_idx:
-                return "/objs/{}".format(idx)
-        return "/no_obj/{}".format(obj_idx)
-
-    def find_path_scene(self, scn_idx):
-        for idx, rec in enumerate(self.sim.scenes):
-            if rec.idx == scn_idx:
-                return "/scenes/{}".format(idx)
-        return "/no_scene/{}".format(scn_idx)
-
-    def find_path_obj_scene(self, rec_idx):
-        for idx, rec in enumerate(self.sim.objects):
-            if rec.idx == rec_idx:
-                return "/objs/{}".format(idx)
-        for idx, rec in enumerate(self.sim.scenes):
-            if rec.idx == rec_idx:
-                return "/scenes/{}".format(idx)
-        return "/no_obj_scene/{}".format(rec_idx)
+    #def find_path_res(self, res):
+    #    for idx, res_id in enumerate(self.sim.resord):
+    #        if res_id == res:
+    #            return "/res/all/{}".format(idx)
+    #    return "/no_res/{}".format(res)
+
+    #def find_path_obj(self, obj_idx):
+    #    for idx, rec in enumerate(self.sim.objects):
+    #        if rec.idx == obj_idx:
+    #            return "/objs/{}".format(idx)
+    #    return "/no_obj/{}".format(obj_idx)
+
+    #def find_path_scene(self, scn_idx):
+    #    for idx, rec in enumerate(self.sim.scenes):
+    #        if rec.idx == scn_idx:
+    #            return "/scenes/{}".format(idx)
+    #    return "/no_scene/{}".format(scn_idx)
+
+    #def find_path_obj_scene(self, rec_idx):
+    #    for idx, rec in enumerate(self.sim.objects):
+    #        if rec.idx == rec_idx:
+    #            return "/objs/{}".format(idx)
+    #    for idx, rec in enumerate(self.sim.scenes):
+    #        if rec.idx == rec_idx:
+    #            return "/scenes/{}".format(idx)
+    #    return "/no_obj_scene/{}".format(rec_idx)
 
     def fmt_hl_rec(self, lst, pref, rec_idx, full = False):
         for idx, rec in enumerate(lst):
@@ -679,11 +705,11 @@ class App(tkinter.Frame):
                 return "/invntr/{}".format(idx)
         return "/no_invntr/{}".format(key)
 
-    def find_path_dlggrp(self, grp_idx):
-        for idx, grp in enumerate(self.sim.dlgs):
-            if grp.idx == grp_idx:
-                return "/dlgs/{}".format(idx)
-        return "/no_dlgs/{}".format(grp_idx)
+    #def find_path_dlggrp(self, grp_idx):
+    #    for idx, grp in enumerate(self.sim.dlgs):
+    #        if grp.idx == grp_idx:
+    #            return "/dlgs/{}".format(idx)
+    #    return "/no_dlgs/{}".format(grp_idx)
 
     def fmt_hl_msg(self, obj_idx, full = False):
         return self.fmt_hl_rec(self.sim.msgs, "msgs", obj_idx, full)
@@ -1331,42 +1357,37 @@ class App(tkinter.Frame):
             
     def path_help(self, path):
         self.switch_view(0)
-
-        lfn = os.path.join(self.app_path, "help", "list")
-        hi = []
-        f = open(lfn, "rb")
-        try:
-            for item in f.readlines():
-                item = item.decode("UTF-8").strip()
-                if not item: continue
-                try:
-                    hf = open(os.path.join(self.app_path, 
-                        "help", item + ".txt"), "rb")
-                    try:
-                        for line in hf.readlines():
-                            line = line.decode("UTF-8").strip()                    
-                            break
-                    finally:
-                        hf.close()
-                except:
-                    line = item                    
-                hi.append([item, line])
-        finally:
-            f.close()
-
         if self.last_path[:1] != ("help",):
             self.update_gui("Help")
-            for item, line in hi:
-                self.insert_lb_act(line, ["help", item])
+            # build help index
+            lfn = os.path.join(self.app_path, "help", "list")
+            f = open(lfn, "rb")
+            try:
+                for item in f.readlines():
+                    item = item.decode("UTF-8").strip()
+                    if not item: continue
+                    try:
+                        hf = open(os.path.join(self.app_path, 
+                            "help", item + ".txt"), "rb")
+                        try:
+                            for line in hf.readlines():
+                                line = line.decode("UTF-8").strip()                    
+                                break
+                        finally:
+                            hf.close()
+                    except:
+                        line = item                    
+                    self.insert_lb_act(line, ["help", item], item)
+            finally:
+                f.close()
+
             self.insert_lb_act("-", None)
             self.insert_lb_act("Outline", [])
 
         # change
         if len(path) > 1:
             # parts
-            for idx, (item, line) in enumerate(hi):
-                if item == path[1]:
-                    self.select_lb_item(idx)
+            self.select_lb_item(path[1])
             self.clear_info()
             hfn = os.path.join(self.app_path, "help", path[1] + ".txt")
             try:
@@ -1385,7 +1406,6 @@ class App(tkinter.Frame):
                     format(hlesc(hfn), hlesc(traceback.format_exc())))
         else:
             self.select_lb_item(None)
-
         
 
     def on_open_data(self):


Commit: c2f1863df4280fae012fcae544e55fd0261a5d13
    https://github.com/scummvm/scummvm-tools/commit/c2f1863df4280fae012fcae544e55fd0261a5d13
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: refactor onjs and scenes

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 8ac6d45b3..e729ffff4 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -552,28 +552,7 @@ class App(tkinter.Frame):
                     mode = 0
                 else:
                     curr_tag += ch
-        if len(curr_text) > 0:                            lfn = os.path.join(self.app_path, "help", "list")
-        hi = []
-        f = open(lfn, "rb")
-        try:
-            for item in f.readlines():
-                item = item.decode("UTF-8").strip()
-                if not item: continue
-                try:
-                    hf = open(os.path.join(self.app_path, 
-                        "help", item + ".txt"), "rb")
-                    try:
-                        for line in hf.readlines():
-                            line = line.decode("UTF-8").strip()                    
-                            break
-                    finally:
-                        hf.close()
-                except:
-                    line = item                    
-                hi.append([item, line])
-        finally:
-            f.close()
-
+        if len(curr_text) > 0: 
             self.text_view.insert(tkinter.INSERT, curr_text, \
                 tuple([x for x in tags for x in x]))
         
@@ -670,39 +649,43 @@ class App(tkinter.Frame):
     #            return "/scenes/{}".format(idx)
     #    return "/no_obj_scene/{}".format(rec_idx)
 
-    def fmt_hl_rec(self, lst, pref, rec_idx, full = False):
-        for idx, rec in enumerate(lst):
-            if rec.idx == rec_idx:
-                fmt = fmt_hl("/{}/{}".format(pref, idx), str(rec_idx))
-                if full:
-                    try:
-                        fmt += " (0x{:X}) - {}".format(rec.idx, hlesc(rec.name))
-                    except:
-                        fmt += " (0x{:X})".format(rec.idx)
-                return fmt
-        return "{} (0x{:X})".format(rec_idx, rec_idx)
+    def fmt_hl_rec(self, lst, lst_idx, pref, rec_id, full = False):
+        if rec_id in lst_idx:
+            fmt = fmt_hl("/{}/{}".format(pref, rec_id), str(rec_id))
+            if full:
+                try:
+                    rec = lst[lst_idx[rec_id]]
+                    fmt += " (0x{:X}) - {}".format(rec_id, hlesc(rec.name))
+                except:
+                    fmt += " (0x{:X})".format(rec_id)
+            return fmt
+        return "{} (0x{:X})".format(rec_id, rec_id)
         
-    def fmt_hl_obj(self, obj_idx, full = False):
-        return self.fmt_hl_rec(self.sim.objects, "objs", obj_idx, full)
+    def fmt_hl_obj(self, obj_id, full = False):
+        return self.fmt_hl_rec(self.sim.objects, self.sim.obj_idx, "objs", 
+            obj_id, full)
         
-    def fmt_hl_scene(self, scn_idx, full = False):
-        return self.fmt_hl_rec(self.sim.scenes, "scenes", scn_idx, full)
-
-    def fmt_hl_obj_scene(self, rec_idx, full = False):
-        if rec_idx in self.sim.obj_idx:
-            return self.fmt_hl_rec(self.sim.objects, "objs", rec_idx, full)
-        return self.fmt_hl_rec(self.sim.scenes, "scenes", rec_idx, full)
+    def fmt_hl_scene(self, scn_id, full = False):
+        return self.fmt_hl_rec(self.sim.scenes, self.sim.scn_idx, "scenes", 
+            scn_id, full)
+
+    def fmt_hl_obj_scene(self, rec_id, full = False):
+        if rec_id in self.sim.obj_idx:
+            return self.fmt_hl_rec(self.sim.objects, self.sim.obj_idx, "objs", 
+                rec_id, full)
+        return self.fmt_hl_rec(self.sim.scenes, self.sim.scn_idx, "scenes", 
+            rec_id, full)
         
     def find_path_name(self, key):
-        for idx, name in enumerate(self.sim.namesord):
+        for name_id, name in enumerate(self.sim.namesord):
             if name == key:
-                return "/names/{}".format(idx)
+                return "/names/{}".format(name_id)
         return "/no_name/{}".format(key)
 
     def find_path_invntr(self, key):
-        for idx, name in enumerate(self.sim.invntrord):
+        for inv_id, name in enumerate(self.sim.invntrord):
             if name == key:
-                return "/invntr/{}".format(idx)
+                return "/invntr/{}".format(inv_id)
         return "/no_invntr/{}".format(key)
 
     #def find_path_dlggrp(self, grp_idx):
@@ -711,11 +694,16 @@ class App(tkinter.Frame):
     #            return "/dlgs/{}".format(idx)
     #    return "/no_dlgs/{}".format(grp_idx)
 
-    def fmt_hl_msg(self, obj_idx, full = False):
-        return self.fmt_hl_rec(self.sim.msgs, "msgs", obj_idx, full)
+    def fmt_hl_msg(self, msg_id, full = False):
+        msg_idx = []
+        if msg_id < len(self.sim.msgs):
+            msg_idx[msg_id] = self.sim.msgs[msg_id]
+        return self.fmt_hl_rec(self.sim.msgs, msg_idx, "msgs", 
+            msg_id, full)
 
-    def fmt_hl_dlg(self, grp_idx, full = False):
-        return self.fmt_hl_rec(self.sim.dlgs, "dlgs", grp_idx, full)
+    def fmt_hl_dlg(self, grp_id, full = False):
+        return self.fmt_hl_rec(self.sim.dlgs, self.sim.dlg_idx, "dlgs", 
+            grp_id, full)
 
     def path_info_outline(self):
         if self.sim is None:
@@ -1003,22 +991,24 @@ class App(tkinter.Frame):
         isobj = (self.curr_path[0] == "objs")
         if isobj:
             lst = self.sim.objects
+            lst_idx = self.sim.obj_idx
         else:
             lst = self.sim.scenes
+            lst_idx = self.sim.scn_idx
         if self.last_path[:1] != (self.curr_path[0],):
             if isobj:
                 self.update_gui("Objects ({})".format(len(lst)))
             else:
                 self.update_gui("Scenes ({})".format(len(lst)))
-            for idx, rec in enumerate(lst):
+            for rec in lst:
                 self.insert_lb_act("{} - {}".format(rec.idx, rec.name), \
-                    [self.curr_path[0], idx])
+                    [self.curr_path[0], rec.idx], rec.idx)
         # change                
         rec = None
         if len(path) > 1:
             # index
             self.select_lb_item(path[1])
-            rec = lst[path[1]]
+            rec = lst_idx[path[1]]
         else:
             self.select_lb_item(None)
         # display


Commit: 9b7ff2f6ff4dafeff8340e698a1bffee325819f3
    https://github.com/scummvm/scummvm-tools/commit/9b7ff2f6ff4dafeff8340e698a1bffee325819f3
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: refactor

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index e729ffff4..5245c10a4 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -98,6 +98,7 @@ class App(tkinter.Frame):
         else:
             self.app_path = __file__
         self.app_path = os.path.abspath(os.path.dirname(self.app_path))
+        self.start_path = "/about"
         # gui
         self.path_handler = {}
         self.curr_main = -1 # 0 - frame, 1 - canvas
@@ -195,11 +196,7 @@ class App(tkinter.Frame):
         self.path_handler["help"] = self.path_help
         
         self.update_after()
-        self.open_path("/about")
-        #self.open_path(self.find_path_scene(36))
-        #self.open_path(["res", "flt", "BMP", 7])
-        #self.open_path(["dlgs", 6])
-        #self.open_path(self.find_path_obj(448))
+        self.open_path(self.start_path)
 
     def create_menu(self):
         self.menubar = tkinter.Menu(self.master)
@@ -649,32 +646,28 @@ class App(tkinter.Frame):
     #            return "/scenes/{}".format(idx)
     #    return "/no_obj_scene/{}".format(rec_idx)
 
-    def fmt_hl_rec(self, lst, lst_idx, pref, rec_id, full = False):
+    def fmt_hl_rec(self, lst_idx, pref, rec_id, full = False):
         if rec_id in lst_idx:
             fmt = fmt_hl("/{}/{}".format(pref, rec_id), str(rec_id))
             if full:
                 try:
-                    rec = lst[lst_idx[rec_id]]
-                    fmt += " (0x{:X}) - {}".format(rec_id, hlesc(rec.name))
+                    fmt += " (0x{:X}) - {}".format(rec_id, 
+                        lst_idx[rec_id].name)
                 except:
                     fmt += " (0x{:X})".format(rec_id)
             return fmt
         return "{} (0x{:X})".format(rec_id, rec_id)
         
     def fmt_hl_obj(self, obj_id, full = False):
-        return self.fmt_hl_rec(self.sim.objects, self.sim.obj_idx, "objs", 
-            obj_id, full)
+        return self.fmt_hl_rec(self.sim.obj_idx, "objs", obj_id, full)
         
     def fmt_hl_scene(self, scn_id, full = False):
-        return self.fmt_hl_rec(self.sim.scenes, self.sim.scn_idx, "scenes", 
-            scn_id, full)
+        return self.fmt_hl_rec(self.sim.scn_idx, "scenes", scn_id, full)
 
     def fmt_hl_obj_scene(self, rec_id, full = False):
         if rec_id in self.sim.obj_idx:
-            return self.fmt_hl_rec(self.sim.objects, self.sim.obj_idx, "objs", 
-                rec_id, full)
-        return self.fmt_hl_rec(self.sim.scenes, self.sim.scn_idx, "scenes", 
-            rec_id, full)
+            return self.fmt_hl_rec(self.sim.obj_idx, "objs", rec_id, full)
+        return self.fmt_hl_rec(self.sim.scn_idx, "scenes", rec_id, full)
         
     def find_path_name(self, key):
         for name_id, name in enumerate(self.sim.namesord):
@@ -688,22 +681,14 @@ class App(tkinter.Frame):
                 return "/invntr/{}".format(inv_id)
         return "/no_invntr/{}".format(key)
 
-    #def find_path_dlggrp(self, grp_idx):
-    #    for idx, grp in enumerate(self.sim.dlgs):
-    #        if grp.idx == grp_idx:
-    #            return "/dlgs/{}".format(idx)
-    #    return "/no_dlgs/{}".format(grp_idx)
-
     def fmt_hl_msg(self, msg_id, full = False):
-        msg_idx = []
+        msg_idx = {}
         if msg_id < len(self.sim.msgs):
             msg_idx[msg_id] = self.sim.msgs[msg_id]
-        return self.fmt_hl_rec(self.sim.msgs, msg_idx, "msgs", 
-            msg_id, full)
+        return self.fmt_hl_rec(msg_idx, "msgs", msg_id, full)
 
     def fmt_hl_dlg(self, grp_id, full = False):
-        return self.fmt_hl_rec(self.sim.dlgs, self.sim.dlg_idx, "dlgs", 
-            grp_id, full)
+        return self.fmt_hl_rec(self.sim.dlg_idx, "dlgs", grp_id, full)
 
     def path_info_outline(self):
         if self.sim is None:
@@ -760,10 +745,21 @@ class App(tkinter.Frame):
     def path_parts(self, path):
         if self.sim is None:
             return self.path_default([])
+        def parsepart(part_id):
+            return pnum, cnum
+
         if self.last_path[:1] != ("parts",):
             self.update_gui("Parts ({})".format(len(self.sim.parts)))
-            for idx, name in enumerate(self.sim.parts):
-                self.insert_lb_act(name, ["parts", idx])
+            for name in self.sim.parts:
+                pnum = name[5:]
+                cnum = pnum.split("Chapter", 1)
+                if len(cnum) > 1:
+                    pnum = int(cnum[0].strip(), 10)
+                    cnum = int(cnum[1].strip(), 10)
+                else:
+                    cnum = 0
+                part_id = "{}.{}".format(pnum, cnum)
+                self.insert_lb_act(name, ["parts", part_id], part_id)
             if len(self.sim.parts) == 0:
                 # Option to fix paths
                 def fix_paths():
@@ -781,15 +777,10 @@ class App(tkinter.Frame):
         if len(path) > 1:
             # parts
             self.select_lb_item(path[1])
-            part_id = self.sim.parts[path[1]]
-            # parse
-            pnum = part_id[5:]
-            cnum = pnum.split("Chapter", 1)
-            if len(cnum) > 1:
-                pnum = int(cnum[0].strip(), 10)
-                cnum = int(cnum[1].strip(), 10)
-            else:
-                cnum = 0
+            part_id = path[1]
+            part_id = part_id.split(".", 1)
+            pnum = int(part_id[0])
+            cnum = int(part_id[1])
             self.sim.open_part(pnum, cnum)
             self.clear_hist()
         else:
@@ -815,6 +806,12 @@ class App(tkinter.Frame):
             return self.path_default(path)
 
     def path_res_open(self, pref, res_id, mode):
+        if res_id not in self.sim.res:        
+            self.switch_view(0)
+            self.clear_info()
+            self.add_info("<b>Resource</b> \"{}\" not found\n".format(res_id))
+            return
+        self.select_lb_item(res_id)
         resref = "/" + "/".join([str(x) for x in pref])
         if len(mode) == 0:
             self.switch_view(0)
@@ -937,6 +934,7 @@ class App(tkinter.Frame):
         for ft in ftk:
             self.add_info("  <a href=\"/res/flt/{}\">{}</a>: {}\n".format(
                 ft, ft, fts[ft]))
+        self.select_lb_item(None)
     
     def on_path_res_info(self):
         self.switch_view(0)
@@ -945,22 +943,18 @@ class App(tkinter.Frame):
         self.switch_view(1)
 
     def path_res_all(self, path):
-        if self.last_path[:2] != ("res", "all",):
+        if self.last_path[:2] != ("res", "all",) and self.last_path != ("res",):
             self.update_gui("Resources ({})".format(len(self.sim.res)))
             #self.add_toolbtn("Info", self.on_path_res_info)
             #self.add_toolbtn("View", self.on_path_res_view)
-            for idx, res_id in enumerate(self.sim.resord):
+            for res_id in self.sim.resord:
                     self.insert_lb_act("{} - {}".format(\
-                res_id, self.sim.res[res_id]), ["res", "all", idx])
+                res_id, self.sim.res[res_id]), ["res", "all", res_id], res_id)
         # change                
         if len(path) > 2:
-            # parts
-            self.select_lb_item(path[2])
-            res_id = self.sim.resord[path[2]]
-            self.path_res_open(path[:3], res_id, path[3:])
+            self.path_res_open(path[:3], path[2], path[3:])
         else:
             self.path_res_status()
-            self.select_lb_item(None)
 
     def path_res_flt(self, path):
         lst = []
@@ -971,18 +965,15 @@ class App(tkinter.Frame):
             self.update_gui("Resources {} ({})".format(path[2], len(lst)))
             self.insert_lb_act("All", "/res")
             self.insert_lb_act("-", None)
-            for idx, res_id in enumerate(lst):
+            for res_id in lst:
                     self.insert_lb_act("{} - {}".format(\
-                res_id, self.sim.res[res_id]), ["res", "flt", path[2], idx])
+                res_id, self.sim.res[res_id]), ["res", "flt", path[2], res_id], 
+                    res_id)
         # change                
         if len(path) > 3:
-            # parts
-            self.select_lb_item(path[3] + 2)
-            res_id = lst[path[3]]
-            self.path_res_open(path[:4], res_id, path[4:])
+            self.path_res_open(path[:4], path[3], path[4:])
         else:
             self.path_res_status()
-            self.select_lb_item(None)
 
     def path_objs_scenes(self, path):
         if self.sim is None:
@@ -1008,12 +999,18 @@ class App(tkinter.Frame):
         if len(path) > 1:
             # index
             self.select_lb_item(path[1])
-            rec = lst_idx[path[1]]
+            try:
+                rec = lst_idx[path[1]]
+            except:
+                pass
         else:
             self.select_lb_item(None)
         # display
         self.clear_info()
         if not rec:
+            if len(path) > 1:
+                self.add_info("Item \"{}\" at <b>{}</b> not found\n\n".format(
+                    path[1], path[0]))
             self.add_info("Select item from list\n")
         else:
             # record info
@@ -1026,7 +1023,7 @@ class App(tkinter.Frame):
                     "Alias") + ":  {}\n".format(
                         hlesc(self.sim.names[rec.name])))
             if rec.name in self.sim.invntr:
-                self.add_info("  " + fmt_hl(self.find_path_name(rec.name), 
+                self.add_info("  " + fmt_hl(self.find_path_invntr(rec.name), 
                     "Invntr") + ": {}\n".format(
                         hlesc(self.sim.invntr[rec.name])))
             # references / backreferences                    
@@ -1106,10 +1103,9 @@ class App(tkinter.Frame):
                 self.add_info("\n<b>Used resources</b>: {}\n".\
                     format(len(resused)))
                 for res_id in resused:
-                    self.add_info("  <a href=\"{}\">{}</a> (0x{:X}) - {}\n".\
-                        format(self.find_path_res(res_id), res_id, res_id, \
-                        hlesc(self.sim.res[res_id])))
-
+                    self.add_info("  " + fmt_hl("/res/all/{}".format(res_id), 
+                        "{}".format(res_id)) + " (0x{:X}) - {}\n".format(res_id,
+                            hlesc(self.sim.res[res_id])))
             if len(dlgused) > 0:
                 self.add_info("\n<b>Used dialog groups</b>: {}\n".\
                     format(len(dlgused)))
@@ -1191,26 +1187,30 @@ class App(tkinter.Frame):
                 if len(capt) > 25:
                     capt = capt[:25] + "|"
                 self.insert_lb_act("{} - {}".format(msg.idx, capt),
-                    ["msgs", idx])
+                    ["msgs", idx], idx)
         # change
         msg = None
         if len(path) > 1:
-            # parts
+            # index
             self.select_lb_item(path[1])
-            msg = self.sim.msgs[path[1]]
+            try:
+                msg = self.sim.msgs[path[1]]
+            except:
+                pass
         else:
             self.select_lb_item(None)
         # display
         self.clear_info()
         if not msg:
-            self.add_info("Select <b>message</b>\n")
+            if len(path) > 1:
+                self.add_info("<b>MEssage</b> \"{}\" not found\n\n".format(
+                    path[1]))
+            self.add_info("Select <b>message</b> from list\n")
         else:
             # msg info
             self.add_info("<b>Message</b>: {}\n".format(path[1]))
             self.add_info("  wav:    {}\n".format(msg.wav))
-            self.add_info("  object: <a href=\"{}\">{}</a> (0x{:X}) - {}\n".\
-                format(self.find_path_obj(msg.arg1), msg.arg1, msg.arg1,
-                    msg.obj.name))
+            self.add_info("  object: " + self.fmt_hl_obj(msg.obj.idx) + "\n")
             self.add_info("  arg2:   {} (0x{:X})\n".format(msg.arg2, msg.arg2))
             self.add_info("  arg3:   {} (0x{:X})\n".format(msg.arg3, msg.arg3))
             self.add_info("\n{}\n".format(hlesc(msg.name)))
@@ -1231,21 +1231,28 @@ class App(tkinter.Frame):
         self.switch_view(0)
         if self.last_path[:1] != ("dlgs",):
             self.update_gui("Dialog groups ({})".format(len(self.sim.dlgs)))
-            for idx, grp in enumerate(self.sim.dlgs):
+            for grp in self.sim.dlgs:
                 self.insert_lb_act("{} (0x{:X})".format(grp.idx, grp.idx),
-                    ["dlgs", idx])
+                    ["dlgs", grp.idx], grp.idx)
+
         # change
         grp = None
         if len(path) > 1:
-            # parts
+            # index
             self.select_lb_item(path[1])
-            grp = self.sim.dlgs[path[1]]
+            try:
+                grp = self.sim.dlg_idx[path[1]]
+            except:
+                pass
         else:
             self.select_lb_item(None)
         # display
         self.clear_info()
         if not grp:
-            self.add_info("Select <b>dialog group</b>\n")
+            if len(path) > 1:
+                self.add_info("<b>Dialog</b> \"{}\" not found\n\n".format(
+                    path[1]))
+            self.add_info("Select <b>dialog group</b> from list\n")
         else:
             # grp info
             self.add_info("<b>Dialog group</b>: {} (0x{:X})\n".format(\
@@ -1435,6 +1442,8 @@ def main():
     app = App(master = root)
     if len(sys.argv) > 1:
         app.open_data_from(sys.argv[1])
+    if len(sys.argv) > 2:
+        app.start_path = sys.argv[2]
     app.mainloop()
 
     
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index acc943436..86cbdb2e5 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -373,6 +373,7 @@ class Engine:
                 f.close()
 
         self.dlgs = []
+        self.dlg_idx = {}
         self.dlgops = []
         # DIALOGUES.FIX
         fp = self.curr_path + "dialogue.fix"
@@ -388,6 +389,7 @@ class Engine:
                     self.dlgs.append(grp)
                 opref = {}
                 for grp in self.dlgs:
+                    self.dlg_idx[grp.idx] = grp
                     grp.acts = []
                     for i in range(grp.num_acts):
                         temp = f.read(16)


Commit: 92814b2799c5e9ff101d976f419b6f51c7b0c54a
    https://github.com/scummvm/scummvm-tools/commit/92814b2799c5e9ff101d976f419b6f51c7b0c54a
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: refactor

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 5245c10a4..b22efa402 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -190,6 +190,7 @@ class App(tkinter.Frame):
         self.path_handler["invntr"] = self.path_invntr
         self.path_handler["msgs"] = self.path_msgs
         self.path_handler["dlgs"] = self.path_dlgs
+        self.path_handler["casts"] = self.path_casts
         self.path_handler["test"] = self.path_test
         self.path_handler["about"] = self.path_about
         self.path_handler["support"] = self.path_support
@@ -741,6 +742,7 @@ class App(tkinter.Frame):
             for name, act in acts:
                 self.insert_lb_act(name, act)
 
+    
 
     def path_parts(self, path):
         if self.sim is None:
@@ -1125,19 +1127,22 @@ class App(tkinter.Frame):
         if self.last_path[:1] != ("names",):
             self.update_gui("Names ({})".format(len(self.sim.names)))
             for idx, name in enumerate(self.sim.namesord):
-                self.insert_lb_act(name, ["names", idx])
+                self.insert_lb_act(name, ["names", idx], idx)
         # change
         name = None
         if len(path) > 1:
             # parts
             self.select_lb_item(path[1])
-            name = self.sim.namesord[path[1]]
+            try:
+                name = self.sim.namesord[path[1]]
+            except:
+                pass
         else:
             self.select_lb_item(None)
         # display
         self.clear_info()
         if not name:
-            self.add_info("Select <b>name</b>\n")
+            self.add_info("Select <b>name</b> from list\n")
         else:
             # name info
             self.add_info("<b>Alias</b>: {}\n".format(hlesc(name)))
@@ -1300,7 +1305,9 @@ class App(tkinter.Frame):
             self.add_info("\n<b>Used by scenes</b>:\n")
             usedby(self.sim.scenes)
             
-
+    def path_casts(self, path):
+        pass
+        
     def path_test(self, path):
         self.update_gui("Test {}".format(path[1]))
         self.insert_lb_act("Outline", [])
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 86cbdb2e5..4ae1e17a7 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -345,6 +345,19 @@ class Engine:
             self.invntr = ini["ALL"]
             self.invntrord = ini["__order__"]["ALL"]
             f.close()
+
+        self.casts = {}
+        self.castsord = []
+        fp = self.curr_path + "cast.ini"
+        if self.fman.exists(fp):
+            f = self.fman.read_file_stream(fp)
+            ini = self.parse_ini(f)
+            self.casts = ini["all"]
+            self.castsord = ini["__order__"]["all"]
+            f.close()
+        # bind casts to objects
+        for cast in self.castsord:
+            #print(cast)
         
     def load_dialogs(self):
         self.msgs = []


Commit: 855c321e5ad5760b8cec4a02a8a98daa93f47cc4
    https://github.com/scummvm/scummvm-tools/commit/855c321e5ad5760b8cec4a02a8a98daa93f47cc4
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Casts data

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index b22efa402..a453cc926 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -236,6 +236,9 @@ class App(tkinter.Frame):
         self.menuedit.add_command(
                 command = lambda: self.open_path("/invntr"),
                 label = "Invntr")
+        self.menuedit.add_command(
+                command = lambda: self.open_path("/casts"),
+                label = "Casts")
         self.menuedit.add_command(
                 command = lambda: self.open_path("/msgs"),
                 label = "Messages")
@@ -742,13 +745,9 @@ class App(tkinter.Frame):
             for name, act in acts:
                 self.insert_lb_act(name, act)
 
-    
-
     def path_parts(self, path):
         if self.sim is None:
             return self.path_default([])
-        def parsepart(part_id):
-            return pnum, cnum
 
         if self.last_path[:1] != ("parts",):
             self.update_gui("Parts ({})".format(len(self.sim.parts)))
@@ -775,22 +774,36 @@ class App(tkinter.Frame):
                     path = self.sim.fman.root = path
                     self.sim.curr_path += "\\"
                 self.add_toolbtn("Fix paths", fix_paths)
-        # change                
+
+        # change
+        part = None
         if len(path) > 1:
             # parts
             self.select_lb_item(path[1])
-            part_id = path[1]
-            part_id = part_id.split(".", 1)
-            pnum = int(part_id[0])
-            cnum = int(part_id[1])
-            self.sim.open_part(pnum, cnum)
-            self.clear_hist()
+            try:
+                part = path[1]
+                part = part.split(".", 1)
+                part[0] = int(part[0])
+                part[1] = int(part[1])
+            except:
+                part = None
+                pass
         else:
             self.select_lb_item(None)
         # display
         self.clear_info()
-        self.add_info("Select <b>part</b>\n\n")
-        self.path_info_outline()
+        if not part:
+            self.add_info("Select <b>part</b>\n\n")
+            self.path_info_outline()
+        else:
+            try:
+                self.clear_hist()
+                self.sim.open_part(part[0], part[1])
+                self.path_info_outline()
+            except:
+                self.add_info("Error open part {} chapter {} - \n\n{}".\
+                    format(part[0], part[1], hlesc(traceback.format_exc())))
+        
 
     def path_res(self, path):
         # res - full list
@@ -1208,14 +1221,15 @@ class App(tkinter.Frame):
         self.clear_info()
         if not msg:
             if len(path) > 1:
-                self.add_info("<b>MEssage</b> \"{}\" not found\n\n".format(
+                self.add_info("<b>Message</b> \"{}\" not found\n\n".format(
                     path[1]))
             self.add_info("Select <b>message</b> from list\n")
         else:
             # msg info
             self.add_info("<b>Message</b>: {}\n".format(path[1]))
             self.add_info("  wav:    {}\n".format(msg.wav))
-            self.add_info("  object: " + self.fmt_hl_obj(msg.obj.idx) + "\n")
+            self.add_info("  object: " + self.fmt_hl_obj(msg.obj.idx, True) + 
+                "\n")
             self.add_info("  arg2:   {} (0x{:X})\n".format(msg.arg2, msg.arg2))
             self.add_info("  arg3:   {} (0x{:X})\n".format(msg.arg3, msg.arg3))
             self.add_info("\n{}\n".format(hlesc(msg.name)))
@@ -1306,7 +1320,37 @@ class App(tkinter.Frame):
             usedby(self.sim.scenes)
             
     def path_casts(self, path):
-        pass
+        if self.sim is None:
+            return self.path_default([])
+        self.switch_view(0)
+        if self.last_path[:1] != ("casts",):
+            self.update_gui("Cast ({})".format(len(self.sim.casts)))
+            for idx, name in enumerate(self.sim.castsord):
+                self.insert_lb_act(name, ["casts", idx], idx)
+        # change
+        name = None
+        if len(path) > 1:
+            # parts
+            self.select_lb_item(path[1])
+            try:
+                name = self.sim.castsord[path[1]]
+            except:
+                pass
+        else:
+            self.select_lb_item(None)
+        # display
+        self.clear_info()
+        if not name:
+            self.add_info("Select <b>cast</b> from list\n")
+        else:
+            # name info
+            self.add_info("<b>Cast</b>: {}\n".format(hlesc(name)))
+            self.add_info("Value: {}\n\n".format(self.sim.casts[name]))
+            # search for objects
+            self.add_info("<b>Applied for</b>:\n")
+            for idx, obj in enumerate(self.sim.objects):
+                if obj.name == name:
+                    self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
         
     def path_test(self, path):
         self.update_gui("Test {}".format(path[1]))
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 4ae1e17a7..d2dc27126 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -358,7 +358,8 @@ class Engine:
         # bind casts to objects
         for cast in self.castsord:
             #print(cast)
-        
+            pass
+            
     def load_dialogs(self):
         self.msgs = []
         # DIALOGUES.LOD


Commit: 5faa3663585917268afedba21f46710b798045cc
    https://github.com/scummvm/scummvm-tools/commit/5faa3663585917268afedba21f46710b798045cc
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Casts data

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index a453cc926..95615c5a3 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -710,6 +710,8 @@ class App(tkinter.Frame):
             format(len(self.sim.names)))
         self.add_info("  Invntr:        <a href=\"/invntr\">{}</a>\n".\
             format(len(self.sim.invntr)))
+        self.add_info("  Casts:         <a href=\"/casts\">{}</a>\n".\
+            format(len(self.sim.casts)))
         self.add_info("  Messages       <a href=\"/msgs\">{}</a>\n".\
             format(len(self.sim.msgs)))
         self.add_info("  Dialog groups: <a href=\"/dlgs\">{}</a>\n".\
@@ -736,6 +738,7 @@ class App(tkinter.Frame):
                 ("Scenes ({})".format(len(self.sim.scenes)), "/scenes"),
                 ("Names ({})".format(len(self.sim.names)), "/names"),
                 ("Invntr ({})".format(len(self.sim.invntr)), "/invntr"),
+                ("Casts ({})".format(len(self.sim.casts)), "/casts"),
                 ("Messages ({})".format(len(self.sim.msgs)), "/msgs"),
                 ("Dialog groups ({})".format(len(self.sim.dlgs)), "/dlgs"),
                 ("-", None),


Commit: d8cc88b71b5299fbe3667f56675946ad502f937d
    https://github.com/scummvm/scummvm-tools/commit/d8cc88b71b5299fbe3667f56675946ad502f937d
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: refactor

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 95615c5a3..28b7b2cb5 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -623,40 +623,13 @@ class App(tkinter.Frame):
             self.hist.append(np)
             self.open_path(np[0], False)
 
-    #def find_path_res(self, res):
-    #    for idx, res_id in enumerate(self.sim.resord):
-    #        if res_id == res:
-    #            return "/res/all/{}".format(idx)
-    #    return "/no_res/{}".format(res)
-
-    #def find_path_obj(self, obj_idx):
-    #    for idx, rec in enumerate(self.sim.objects):
-    #        if rec.idx == obj_idx:
-    #            return "/objs/{}".format(idx)
-    #    return "/no_obj/{}".format(obj_idx)
-
-    #def find_path_scene(self, scn_idx):
-    #    for idx, rec in enumerate(self.sim.scenes):
-    #        if rec.idx == scn_idx:
-    #            return "/scenes/{}".format(idx)
-    #    return "/no_scene/{}".format(scn_idx)
-
-    #def find_path_obj_scene(self, rec_idx):
-    #    for idx, rec in enumerate(self.sim.objects):
-    #        if rec.idx == rec_idx:
-    #            return "/objs/{}".format(idx)
-    #    for idx, rec in enumerate(self.sim.scenes):
-    #        if rec.idx == rec_idx:
-    #            return "/scenes/{}".format(idx)
-    #    return "/no_obj_scene/{}".format(rec_idx)
-
     def fmt_hl_rec(self, lst_idx, pref, rec_id, full = False):
         if rec_id in lst_idx:
             fmt = fmt_hl("/{}/{}".format(pref, rec_id), str(rec_id))
             if full:
                 try:
                     fmt += " (0x{:X}) - {}".format(rec_id, 
-                        lst_idx[rec_id].name)
+                        hlesc(lst_idx[rec_id].name))
                 except:
                     fmt += " (0x{:X})".format(rec_id)
             return fmt
@@ -1136,21 +1109,23 @@ class App(tkinter.Frame):
                     if msg.obj.idx != rec.idx: continue
                     self.add_info("  " + self.fmt_hl_msg(msg.idx, True) + "\n")
 
-    def path_names(self, path):
-        if self.sim is None:
-            return self.path_default([])
+    def path_std_items(self, path, level, guiname, guiitem, lst, lst_idx, 
+            lbmode, cb):
         self.switch_view(0)
-        if self.last_path[:1] != ("names",):
-            self.update_gui("Names ({})".format(len(self.sim.names)))
-            for idx, name in enumerate(self.sim.namesord):
-                self.insert_lb_act(name, ["names", idx], idx)
+        if self.last_path[:level] != path[:level]:
+            self.update_gui("{} ({})".format(guiname, len(lst)))
+            for idx, name in enumerate(lst_idx):
+                lb = name
+                if lbmode == 1:
+                    lb = "{} - {}".format(name, lst[name])
+                self.insert_lb_act(lb, path[:level] + tuple([idx]), idx)
         # change
         name = None
         if len(path) > 1:
-            # parts
+            # lb
             self.select_lb_item(path[1])
             try:
-                name = self.sim.namesord[path[1]]
+                name = lst_idx[path[1]]
             except:
                 pass
         else:
@@ -1158,44 +1133,54 @@ class App(tkinter.Frame):
         # display
         self.clear_info()
         if not name:
-            self.add_info("Select <b>name</b> from list\n")
+            self.add_info("Select <b>{}</b> from list\n".format(guiitem))
         else:
-            # name info
+            # info
+            cb(name)
+        
+
+    def path_names(self, path):
+        if self.sim is None:
+            return self.path_default([])
+        def info(name):
             self.add_info("<b>Alias</b>: {}\n".format(hlesc(name)))
             self.add_info("Value: {}\n\n".format(self.sim.names[name]))
             # search for objects
             self.add_info("<b>Applied for</b>:\n")
-            for idx, obj in enumerate(self.sim.objects):
+            for obj in self.sim.objects:
                 if obj.name == name:
                     self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
-                    
+        return self.path_std_items(path, 1, "Names", "name", self.sim.names, 
+            self.sim.namesord, 0, info)
+                            
     def path_invntr(self, path):
         if self.sim is None:
             return self.path_default([])
-        self.switch_view(0)
-        if self.last_path[:1] != ("invntr",):
-            self.update_gui("Invntr ({})".format(len(self.sim.invntr)))
-            for idx, name in enumerate(self.sim.invntrord):
-                self.insert_lb_act(name, ["invntr", idx])
-        # change
-        name = None
-        if len(path) > 1:
-            # parts
-            self.select_lb_item(path[1])
-            name = self.sim.invntrord[path[1]]
-        # display
-        self.clear_info()
-        if not name:
-            self.add_info("Select <b>invntr</b>\n")
-        else:
-            # invntr info
-            self.add_info("<b>Invntr</b>: {}\n".format(name))
-            self.add_info("{}\n\n".format(hlesc(self.sim.invntr[name])))
+        def info(name):
+            self.add_info("<b>Invntr</b>: {}\n".format(hlesc(name)))
+            self.add_info("{}\n\n".format(self.sim.invntr[name]))
+            # search for objects
+            self.add_info("<b>Applied for</b>:\n")
+            for obj in self.sim.objects:
+                if obj.name == name:
+                    self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
+        return self.path_std_items(path, 1, "Invntr", "invntr", self.sim.invntr, 
+            self.sim.invntrord, 0, info)
+
+
+    def path_casts(self, path):
+        if self.sim is None:
+            return self.path_default([])
+        def info(name):
+            self.add_info("<b>Cast</b>: {}\n".format(hlesc(name)))
+            self.add_info("Value: {}\n\n".format(self.sim.casts[name]))
             # search for objects
             self.add_info("<b>Applied for</b>:\n")
             for idx, obj in enumerate(self.sim.objects):
                 if obj.name == name:
                     self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
+        return self.path_std_items(path, 1, "Cast", "cast", self.sim.casts, 
+            self.sim.castsord, 0, info)
 
     def path_msgs(self, path):
         if self.sim is None:
@@ -1322,38 +1307,6 @@ class App(tkinter.Frame):
             self.add_info("\n<b>Used by scenes</b>:\n")
             usedby(self.sim.scenes)
             
-    def path_casts(self, path):
-        if self.sim is None:
-            return self.path_default([])
-        self.switch_view(0)
-        if self.last_path[:1] != ("casts",):
-            self.update_gui("Cast ({})".format(len(self.sim.casts)))
-            for idx, name in enumerate(self.sim.castsord):
-                self.insert_lb_act(name, ["casts", idx], idx)
-        # change
-        name = None
-        if len(path) > 1:
-            # parts
-            self.select_lb_item(path[1])
-            try:
-                name = self.sim.castsord[path[1]]
-            except:
-                pass
-        else:
-            self.select_lb_item(None)
-        # display
-        self.clear_info()
-        if not name:
-            self.add_info("Select <b>cast</b> from list\n")
-        else:
-            # name info
-            self.add_info("<b>Cast</b>: {}\n".format(hlesc(name)))
-            self.add_info("Value: {}\n\n".format(self.sim.casts[name]))
-            # search for objects
-            self.add_info("<b>Applied for</b>:\n")
-            for idx, obj in enumerate(self.sim.objects):
-                if obj.name == name:
-                    self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
         
     def path_test(self, path):
         self.update_gui("Test {}".format(path[1]))


Commit: 82d97637d1997b2d9b15e8a577829297100b0557
    https://github.com/scummvm/scummvm-tools/commit/82d97637d1997b2d9b15e8a577829297100b0557
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: refactor

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 28b7b2cb5..6cf64dba7 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -130,6 +130,7 @@ class App(tkinter.Frame):
         self.toolbar.pack(fill = tkinter.BOTH)
         btns = [
             ["Outline", lambda: self.open_path("")],
+            ["Help", self.on_help],
             [None, None],
             ["<-", self.on_back],
             ["->", self.on_forward],


Commit: e5adc359d0f3544b05d36b685bacf700ae06f156
    https://github.com/scummvm/scummvm-tools/commit/e5adc359d0f3544b05d36b685bacf700ae06f156
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Context help

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 6cf64dba7..a19934122 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -610,6 +610,12 @@ class App(tkinter.Frame):
         self.hist = self.hist[-1:]
         self.histf = []
 
+    def on_help(self):
+        if len(self.curr_path) > 0:
+            self.open_path(["help", self.curr_path[0]])
+        else:
+            self.open_path(["help"])
+
     def on_back(self):
         if len(self.hist) > 1:
             np = self.hist[-2:-1][0]


Commit: fc10e027f2ff70322d1c92c1e41a00a689e175a0
    https://github.com/scummvm/scummvm-tools/commit/fc10e027f2ff70322d1c92c1e41a00a689e175a0
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Info, help pages

Changed paths:
    engines/petka/help/index.txt
    engines/petka/help/list
    engines/petka/p12explore.py
    engines/petka/petka/__init__.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/help/index.txt b/engines/petka/help/index.txt
index 4a2dcad34..69a5846bc 100644
--- a/engines/petka/help/index.txt
+++ b/engines/petka/help/index.txt
@@ -5,3 +5,21 @@
  * <a href="/help/changes">Что нового</a>
  * <a href="/help/faq">Часто задаваемые вопросы</a>
 
+Отображаемые разделы
+
+ * <a href="/help/parts">Выбор части</a>
+ * <a href="/help/res">Ресурсы</a>
+ * <a href="/help/objs">Объекты</a>
+ * <a href="/help/scenes">Сцены</a>
+ * <a href="/help/names">Отображаемные имена</a>
+ * <a href="/help/invntr">Инвентарь</a>
+ * <a href="/help/casts">Цвета предметов</a>
+ * <a href="/help/msgs">Собщения</a>
+ * <a href="/help/dlgs">Группы диалогов</a>
+
+Прочая информация
+
+ * <a href="/help/support">Информация для подержки</a>
+ * <a href="/help/info">Справочники</a>
+ * <a href="/help/about">О программе</a>
+
diff --git a/engines/petka/help/list b/engines/petka/help/list
index 74879b370..62df0df1f 100644
--- a/engines/petka/help/list
+++ b/engines/petka/help/list
@@ -1,4 +1,16 @@
 index
 changes
 faq
+parts
+res
+objs
+scenes
+names
+invntr
+casts
+msgs
+dlgs
+support
+info
+about
 
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index a19934122..97a4b5881 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -196,6 +196,7 @@ class App(tkinter.Frame):
         self.path_handler["about"] = self.path_about
         self.path_handler["support"] = self.path_support
         self.path_handler["help"] = self.path_help
+        self.path_handler["info"] = self.path_info
         
         self.update_after()
         self.open_path(self.start_path)
@@ -275,6 +276,9 @@ class App(tkinter.Frame):
         self.menuhelp.add_command(
                 command = lambda: self.open_path("/support"),
                 label = "Support")
+        self.menuhelp.add_command(
+                command = lambda: self.open_path("/info"),
+                label = "Info")
         self.menuhelp.add_command(
                 command = lambda: self.open_path("/about"),
                 label = "About")
@@ -721,9 +725,9 @@ class App(tkinter.Frame):
                 ("Casts ({})".format(len(self.sim.casts)), "/casts"),
                 ("Messages ({})".format(len(self.sim.msgs)), "/msgs"),
                 ("Dialog groups ({})".format(len(self.sim.dlgs)), "/dlgs"),
-                ("-", None),
-                ("Test image", ["test", "image"]),
-                ("Test info", ["test","info"]),
+                #("-", None),
+                #("Test image", ["test", "image"]),
+                #("Test info", ["test","info"]),
             ]
             for name, act in acts:
                 self.insert_lb_act(name, act)
@@ -1145,7 +1149,6 @@ class App(tkinter.Frame):
             # info
             cb(name)
         
-
     def path_names(self, path):
         if self.sim is None:
             return self.path_default([])
@@ -1368,6 +1371,8 @@ class App(tkinter.Frame):
             
     def path_help(self, path):
         self.switch_view(0)
+        if path == ("help",):
+            path = ("help", "index")
         if self.last_path[:1] != ("help",):
             self.update_gui("Help")
             # build help index
@@ -1418,6 +1423,42 @@ class App(tkinter.Frame):
         else:
             self.select_lb_item(None)
         
+    def path_info(self, path):
+        self.switch_view(0)
+        if self.last_path[:1] != ("info",):
+            self.update_gui("Info")
+            self.insert_lb_act("Opcodes", ["info", "opcodes"], "opcodes")
+            self.insert_lb_act("Dialog opcodes", ["info", "dlgops"], "dlgops")
+        # change
+        name = None
+        if len(path) > 1:
+            # lb
+            self.select_lb_item(path[1])
+            name = path[1]
+        else:
+            self.select_lb_item(None)
+        # display
+        self.clear_info()
+        if not name:
+            self.add_info("Select <b>info</b> from list\n")
+        else:
+            # info
+            if name == "opcodes":
+                self.add_info("<b>Opcodes<b>\n\n")
+                k = list(petka.OPCODES.keys())
+                k.sort()
+                for key in k:
+                    self.add_info("  {} (0x{:X}) - {}\n".format(key, key,
+                        petka.OPCODES[key][0])) 
+            elif name == "dlgops":
+                self.add_info("<b>Dialog opcodes<b>\n\n")
+                k = list(petka.DLGOPS.keys())
+                k.sort()
+                for key in k:
+                    self.add_info("  {} (0x{:X}) - {}\n".format(key, key,
+                        petka.DLGOPS[key][0]))
+            else: 
+                self.add_info("Unknown data type \"{}\"\n".format(hlesc(name)))
 
     def on_open_data(self):
         ft = [\
diff --git a/engines/petka/petka/__init__.py b/engines/petka/petka/__init__.py
index 42388d6d6..40b354c6f 100644
--- a/engines/petka/petka/__init__.py
+++ b/engines/petka/petka/__init__.py
@@ -1,7 +1,7 @@
 
 class EngineError(Exception): pass
 
-from .engine import Engine, OPCODES
+from .engine import Engine, OPCODES, DLGOPS
 from .fman import FileManager
 from .imgbmp import BMPLoader
 from .imgflc import FLCLoader
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index d2dc27126..ce8d4fdd8 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -70,6 +70,13 @@ OPCODES = {
     63: ("TOMAP",       0),
 }
 
+DLGOPS = {
+    1:  ("BREAK",       0),
+    6:  ("RETURN",      0),
+    7:  ("PLAY",        1),
+    8:  ("CIRCLE",      3),
+}
+
 class ScrObject:
     def __init__(self, idx, name):
         self.idx = idx


Commit: 1bd7e2b3b9181aee2a187f1a6eaf82c2b9e2d7cd
    https://github.com/scummvm/scummvm-tools/commit/1bd7e2b3b9181aee2a187f1a6eaf82c2b9e2d7cd
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: help pages

Changed paths:
  A engines/petka/help/about.txt
  A engines/petka/help/casts.txt
  A engines/petka/help/dlgs.txt
  A engines/petka/help/help.txt
  A engines/petka/help/info.txt
  A engines/petka/help/invntr.txt
  A engines/petka/help/msgs.txt
  A engines/petka/help/names.txt
  A engines/petka/help/objs.txt
  A engines/petka/help/parts.txt
  A engines/petka/help/res.txt
  A engines/petka/help/scenes.txt
  A engines/petka/help/support.txt


diff --git a/engines/petka/help/about.txt b/engines/petka/help/about.txt
new file mode 100644
index 000000000..d42abfc4e
--- /dev/null
+++ b/engines/petka/help/about.txt
@@ -0,0 +1,4 @@
+О программе
+
+На этой странице отображаются основные сведения о программе и загруженных 
+данных.
diff --git a/engines/petka/help/casts.txt b/engines/petka/help/casts.txt
new file mode 100644
index 000000000..eade4010a
--- /dev/null
+++ b/engines/petka/help/casts.txt
@@ -0,0 +1,5 @@
+Цвета предметов
+
+На этой странице можно просмотреть цвета описаний предметов на экране.
+
+
diff --git a/engines/petka/help/dlgs.txt b/engines/petka/help/dlgs.txt
new file mode 100644
index 000000000..e95d96cde
--- /dev/null
+++ b/engines/petka/help/dlgs.txt
@@ -0,0 +1,5 @@
+Группы диалогов
+
+На этой странице можно просмотреть все используемые группы диалогов.
+
+
diff --git a/engines/petka/help/help.txt b/engines/petka/help/help.txt
new file mode 100644
index 000000000..b3268bd5b
--- /dev/null
+++ b/engines/petka/help/help.txt
@@ -0,0 +1 @@
+См. <a href="/help">Справка</a>
diff --git a/engines/petka/help/info.txt b/engines/petka/help/info.txt
new file mode 100644
index 000000000..51ea6c817
--- /dev/null
+++ b/engines/petka/help/info.txt
@@ -0,0 +1,7 @@
+Справочники
+
+На этой странице отображаются технические справочники.
+
+ * Коды операций в обработчиках
+ * Коды операций в диалогах
+
diff --git a/engines/petka/help/invntr.txt b/engines/petka/help/invntr.txt
new file mode 100644
index 000000000..7bfdd3be9
--- /dev/null
+++ b/engines/petka/help/invntr.txt
@@ -0,0 +1,5 @@
+Инвентарь
+
+На этой странице можно просмотреть описание предметов инвентаря.
+
+
diff --git a/engines/petka/help/msgs.txt b/engines/petka/help/msgs.txt
new file mode 100644
index 000000000..2221caceb
--- /dev/null
+++ b/engines/petka/help/msgs.txt
@@ -0,0 +1,5 @@
+Сообщения
+
+На этой странице можно просмотреть все используемые сообщения.
+
+
diff --git a/engines/petka/help/names.txt b/engines/petka/help/names.txt
new file mode 100644
index 000000000..11331ac02
--- /dev/null
+++ b/engines/petka/help/names.txt
@@ -0,0 +1,5 @@
+Отображаемые имена
+
+На этой странице можно просмотреть используемые отображаемые имена.
+
+
diff --git a/engines/petka/help/objs.txt b/engines/petka/help/objs.txt
new file mode 100644
index 000000000..3a4f380f7
--- /dev/null
+++ b/engines/petka/help/objs.txt
@@ -0,0 +1,5 @@
+Объекты
+
+На этой странице можно просмотреть используемые объекты и их параметры.
+
+
diff --git a/engines/petka/help/parts.txt b/engines/petka/help/parts.txt
new file mode 100644
index 000000000..0554a03d0
--- /dev/null
+++ b/engines/petka/help/parts.txt
@@ -0,0 +1,6 @@
+Выбор части
+
+На этой странице можно выбрать часть с которой будет в дальнейшем вестись 
+работа.
+
+
diff --git a/engines/petka/help/res.txt b/engines/petka/help/res.txt
new file mode 100644
index 000000000..9060002f3
--- /dev/null
+++ b/engines/petka/help/res.txt
@@ -0,0 +1,5 @@
+Ресурсы
+
+На этой странице можно просмотреть используемые ресурсы (BMP, FLC, AVI и т. д.)
+
+
diff --git a/engines/petka/help/scenes.txt b/engines/petka/help/scenes.txt
new file mode 100644
index 000000000..31e7e9302
--- /dev/null
+++ b/engines/petka/help/scenes.txt
@@ -0,0 +1,5 @@
+Сцены
+
+На этой странице можно просмотреть используемые сцены и их параметры.
+
+
diff --git a/engines/petka/help/support.txt b/engines/petka/help/support.txt
new file mode 100644
index 000000000..878f5cc98
--- /dev/null
+++ b/engines/petka/help/support.txt
@@ -0,0 +1,6 @@
+Информация для поддержки
+
+На этой странице отображаются технические сведения о программе и загруженных 
+данных.
+
+Передайте эти сведения разработчикам, при необходимости.


Commit: 1f44482793c93a988f34674b510f093e179adc92
    https://github.com/scummvm/scummvm-tools/commit/1f44482793c93a988f34674b510f093e179adc92
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fixes

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py
    engines/petka/petka/fman.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 97a4b5881..2a0f2066d 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -736,6 +736,7 @@ class App(tkinter.Frame):
         if self.sim is None:
             return self.path_default([])
 
+        self.switch_view(0)
         if self.last_path[:1] != ("parts",):
             self.update_gui("Parts ({})".format(len(self.sim.parts)))
             for name in self.sim.parts:
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index ce8d4fdd8..9e5b2b352 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -250,8 +250,8 @@ class Engine:
         # load .STR
         strs = ["Flics", "Background", "Wav", "Music", "SFX"]
         for strf in strs:
-            pf = self.fman.find_path(self.curr_path + "bgs.ini")
-            if not pf: continue
+            #pf = self.fman.find_path(self.curr_path + "bgs.ini")
+            #if not pf: continue
             if strf in ini:
                 self.fman.load_store(ini[strf], 1)
         # load script.dat, backgrnd.bg and resources.qrc
diff --git a/engines/petka/petka/fman.py b/engines/petka/petka/fman.py
index e9f9dea76..142597b92 100644
--- a/engines/petka/petka/fman.py
+++ b/engines/petka/petka/fman.py
@@ -67,6 +67,7 @@ class FileManager:
                         format(fname, name))
         # add file descriptor
         self.strfd.append((f, name, tag))
+        print("DEBUG: Loaded store \"{}\"".format(name))
         
     def read_file(self, fname):
         sf = fname.lower().replace("\\", "/")


Commit: 5aa3199fde98d5a12a3aba07d5f79ffcef973c31
    https://github.com/scummvm/scummvm-tools/commit/5aa3199fde98d5a12a3aba07d5f79ffcef973c31
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Open data and select item from command line

Changed paths:
  A engines/petka/help/cmdline.txt
    engines/petka/help/changes.txt
    engines/petka/help/index.txt
    engines/petka/help/list
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 73a82330d..f3e341d8c 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -5,6 +5,7 @@
 ----------------------
 Исправления на странице Support
 Добавлена справка по программе
+Добавлен выбор раздела из командной строки
 
 2014-05-16 версия 0.2d
 ----------------------
diff --git a/engines/petka/help/cmdline.txt b/engines/petka/help/cmdline.txt
new file mode 100644
index 000000000..a0cae8862
--- /dev/null
+++ b/engines/petka/help/cmdline.txt
@@ -0,0 +1,21 @@
+Коммандная строка
+
+Синтаксис вызова программы из коммандной строки
+
+  p12simtran [путь к данным [открываемые разделы]
+
+Путь к данным - путь к файлу или каталогу где находится файл с данными.
+  
+Основные разделы:
+
+ * /parts - выбор части
+ * /parts/2.1 - загрузить часть 2 главу 1
+ * /res/all - ресурсы
+ * /res/all/31000 - загрузить ресурс 31000
+ * /objs - объекты
+ * /scenes - сцены
+ * /msgs - сообщения
+ * /dlgs - группы диалогов
+ 
+ 
+
diff --git a/engines/petka/help/index.txt b/engines/petka/help/index.txt
index 69a5846bc..265425f56 100644
--- a/engines/petka/help/index.txt
+++ b/engines/petka/help/index.txt
@@ -19,6 +19,7 @@
 
 Прочая информация
 
+ * <a href="/help/cmdline">Аргументы коммандной строки</a>
  * <a href="/help/support">Информация для подержки</a>
  * <a href="/help/info">Справочники</a>
  * <a href="/help/about">О программе</a>
diff --git a/engines/petka/help/list b/engines/petka/help/list
index 62df0df1f..a43ec9fd3 100644
--- a/engines/petka/help/list
+++ b/engines/petka/help/list
@@ -10,6 +10,7 @@ invntr
 casts
 msgs
 dlgs
+cmdline
 support
 info
 about
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 2a0f2066d..f435c6fc9 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -98,7 +98,7 @@ class App(tkinter.Frame):
         else:
             self.app_path = __file__
         self.app_path = os.path.abspath(os.path.dirname(self.app_path))
-        self.start_path = "/about"
+        self.start_act = []
         # gui
         self.path_handler = {}
         self.curr_main = -1 # 0 - frame, 1 - canvas
@@ -199,7 +199,16 @@ class App(tkinter.Frame):
         self.path_handler["info"] = self.path_info
         
         self.update_after()
-        self.open_path(self.start_path)
+        repath = "/about"
+        for cmd, arg in self.start_act:
+            if cmd == "load":
+                self.open_data_from(arg)
+                repath = "/"
+            elif cmd == "open":
+                self.open_path(arg)
+                repath = ""
+        if repath:
+            self.open_path(repath)
 
     def create_menu(self):
         self.menubar = tkinter.Menu(self.master)
@@ -1497,9 +1506,9 @@ def main():
     root = tkinter.Tk()
     app = App(master = root)
     if len(sys.argv) > 1:
-        app.open_data_from(sys.argv[1])
-    if len(sys.argv) > 2:
-        app.start_path = sys.argv[2]
+        app.start_act.append(["load", sys.argv[1]])
+    for arg in sys.argv[2:]:
+        app.start_act.append(["open", arg])
     app.mainloop()
 
     


Commit: d1f513f4325d33ab72a08d5021b7c20c998f79f1
    https://github.com/scummvm/scummvm-tools/commit/d1f513f4325d33ab72a08d5021b7c20c998f79f1
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fix dist

Changed paths:
    engines/petka/dist/setup.py


diff --git a/engines/petka/dist/setup.py b/engines/petka/dist/setup.py
index 729cd75b0..fb31ef8dc 100644
--- a/engines/petka/dist/setup.py
+++ b/engines/petka/dist/setup.py
@@ -8,7 +8,7 @@ import sys, os, struct
 # fine tuning.
 buildOptions = dict(packages = ["re", "io", "PIL", "traceback", "zlib", "gzip", "argparse", "struct", "binascii"], \
     excludes = ["_posixsubprocess"],
-    include_files = [],
+    include_files = ["help"],
     compressed = True, silent = True,\
     optimize = 2, copy_dependent_files = True, \
     create_shared_zip = True, include_in_shared_zip = True)


Commit: 30075a24503ffe4ac3cc8018bab3f95d5a3578a8
    https://github.com/scummvm/scummvm-tools/commit/30075a24503ffe4ac3cc8018bab3f95d5a3578a8
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Commant for dialog opcode 8

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index f3e341d8c..c80c386a9 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,6 +1,10 @@
 Что нового
 ==========
 
+2014-05-16 версия 0.2f
+----------------------
+Подсказка при декодировании опкода 8 в дилогах
+
 2014-05-16 версия 0.2e
 ----------------------
 Исправления на странице Support
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index f435c6fc9..006563d64 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -21,7 +21,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2e 2014-05-16"
+VERSION = "v0.2f 2014-05-16"
 
 def hlesc(value):
     if value is None:
@@ -31,6 +31,9 @@ def hlesc(value):
 def fmt_opcode(opcode):
     return petka.OPCODES.get(opcode, ["OP{:04X}".format(opcode)])[0]
 
+def fmt_dlgop(opcode):
+    return petka.DLGOPS.get(opcode, ["OP{:02X}".format(opcode)])[0]
+
 def fmt_hl(loc, desc):
     return "<a href=\"{}\">{}</a>".format(loc, desc)
 
@@ -1294,20 +1297,43 @@ class App(tkinter.Frame):
                 for didx, dlg in enumerate(act.dlgs):
                     self.add_info("    {}) <i>0x{:X} 0x{:X}</i>, ops: {}\n".\
                         format(didx, dlg.arg1, dlg.arg2, len(dlg.ops)))
-                    for op in dlg.ops:
+                    for oidx, op in enumerate(dlg.ops):
                         cmt = ""
-                        msgref = "0x{:X}".format(op.ref)
-                        opcode = "OP{:02X}".format(op.opcode)
-                        if op.opcode == 7:
+                        opref = "0x{:X}".format(op.ref)
+                        opcode = fmt_dlgop(op.opcode)
+                        if op.opcode == 0x7:
                             opcode = "PLAY"
                             if op.msg:
-                                msgref = self.fmt_hl_msg(op.ref)
+                                opref = self.fmt_hl_msg(op.ref)
                                 objref = self.fmt_hl_obj(op.msg.obj.idx)
                                 cmt = " / obj={}, msg={}".\
                                     format(objref, 
                                         self.fmt_hl_msg(op.ref, True))
+                        if op.opcode == 8:
+                            cmt = " / select from "
+                            doarr = []
+                            docurr = []
+                            skiptobrk = False
+                            for op2 in dlg.ops[oidx + 1:]:
+                                #cmt += fmt_dlgop(op2.opcode) + " "
+                                if op2.opcode == 0x1: # BREAK
+                                    doarr.append(docurr)
+                                    skiptobrk = False
+                                    if len(doarr) == op.ref:
+                                        break
+                                    docurr = []
+                                elif op2.opcode == 0x7 and not skiptobrk: # PLAY
+                                    docurr.append(self.fmt_hl_msg(op2.ref))
+                                else:
+                                    docurr = ["complex"]
+                                    
+                                    
+                            if len(doarr) < op.ref:
+                                cmt = " / circle select broken, required={}, "\
+                                    "got={}".format(op.ref, len(doarr))
+                            cmt += ",".join(["+".join(x) for x in doarr])
                         self.add_info("      {} 0x{:X} {}{}\n".\
-                            format(opcode, op.arg, msgref, cmt))
+                            format(opcode, op.arg, opref, cmt))
 
             def usedby(lst):
                 for idx, rec in enumerate(lst):


Commit: aa5eafc13a5572ef3e620483df4fb406bb29a51f
    https://github.com/scummvm/scummvm-tools/commit/aa5eafc13a5572ef3e620483df4fb406bb29a51f
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: code position for dialog opcodes

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index c80c386a9..18339408c 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,6 +1,10 @@
 Что нового
 ==========
 
+2014-05-16 версия 0.2g
+----------------------
+Добавлено отображение позиции дилоговых опкодов в общем блоке кода
+
 2014-05-16 версия 0.2f
 ----------------------
 Подсказка при декодировании опкода 8 в дилогах
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 006563d64..c45a01d17 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -21,7 +21,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2f 2014-05-16"
+VERSION = "v0.2g 2014-05-16"
 
 def hlesc(value):
     if value is None:
@@ -1309,17 +1309,17 @@ class App(tkinter.Frame):
                                 cmt = " / obj={}, msg={}".\
                                     format(objref, 
                                         self.fmt_hl_msg(op.ref, True))
-                        if op.opcode == 8:
-                            cmt = " / select from "
+                        elif op.opcode == 2 or op.opcode == 8: # MENU or CIRCLE
+                            cmt = " / select "
                             doarr = []
                             docurr = []
+                            sellen = op.ref % 0x100
                             skiptobrk = False
                             for op2 in dlg.ops[oidx + 1:]:
-                                #cmt += fmt_dlgop(op2.opcode) + " "
                                 if op2.opcode == 0x1: # BREAK
                                     doarr.append(docurr)
                                     skiptobrk = False
-                                    if len(doarr) == op.ref:
+                                    if len(doarr) == sellen:
                                         break
                                     docurr = []
                                 elif op2.opcode == 0x7 and not skiptobrk: # PLAY
@@ -1328,12 +1328,13 @@ class App(tkinter.Frame):
                                     docurr = ["complex"]
                                     
                                     
-                            if len(doarr) < op.ref:
-                                cmt = " / circle select broken, required={}, "\
-                                    "got={}".format(op.ref, len(doarr))
-                            cmt += ",".join(["+".join(x) for x in doarr])
-                        self.add_info("      {} 0x{:X} {}{}\n".\
-                            format(opcode, op.arg, opref, cmt))
+                            if len(doarr) < sellen:
+                                cmt = " / {} select broken, required={}, "\
+                                    "got={}".format(opcode, sellen, len(doarr))
+                            else:
+                                cmt += ",".join(["+".join(x) for x in doarr])
+                        self.add_info("      <i>{:04X}:</i> {} 0x{:X} {}{}\n".\
+                            format(op.pos, opcode, op.arg, opref, cmt))
 
             def usedby(lst):
                 for idx, rec in enumerate(lst):
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 9e5b2b352..137cb24af 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -72,6 +72,7 @@ OPCODES = {
 
 DLGOPS = {
     1:  ("BREAK",       0),
+    2:  ("MENU",        2),
     6:  ("RETURN",      0),
     7:  ("PLAY",        1),
     8:  ("CIRCLE",      3),
@@ -117,11 +118,12 @@ class DlgObject:
         self.ops = None
 
 class DlgOpObject:
-    def __init__(self, opcode, arg, ref):
+    def __init__(self, opcode, arg, ref, pos):
         self.opcode = opcode
         self.arg = arg
         self.ref = ref
         self.msg = None
+        self.pos = pos
         
 class Engine:
     def __init__(self):
@@ -437,10 +439,10 @@ class Engine:
                             act.dlgs.append(dlg)
                 temp = f.read(4)
                 num_ops = struct.unpack_from("<I", temp)[0]
-                for i in range(num_ops):
+                for oidx, i in enumerate(range(num_ops)):
                     temp = f.read(4)
                     ref, arg, code  = struct.unpack_from("<HBB", temp)
-                    dlgop = DlgOpObject(code, arg, ref)
+                    dlgop = DlgOpObject(code, arg, ref, oidx)
                     if ref < len(self.msgs):
                         dlgop.msg = self.msgs[ref]
                     self.dlgops.append(dlgop)


Commit: 5e4bfe56ef1258886e8be568ff42db410e6d05b7
    https://github.com/scummvm/scummvm-tools/commit/5e4bfe56ef1258886e8be568ff42db410e6d05b7
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Improved help system

Changed paths:
  A engines/petka/help/res_view.txt
    engines/petka/help/changes.txt
    engines/petka/help/res.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 18339408c..d446c5e63 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,6 +1,11 @@
 Что нового
 ==========
 
+2014-05-16 версия 0.2i
+----------------------
+Доработана справочная система.
+
+
 2014-05-16 версия 0.2g
 ----------------------
 Добавлено отображение позиции дилоговых опкодов в общем блоке кода
diff --git a/engines/petka/help/res.txt b/engines/petka/help/res.txt
index 9060002f3..0c3af508b 100644
--- a/engines/petka/help/res.txt
+++ b/engines/petka/help/res.txt
@@ -2,4 +2,4 @@
 
 На этой странице можно просмотреть используемые ресурсы (BMP, FLC, AVI и т. д.)
 
-
+ * <a href="/help/res_view">Подробная информация о ресурсе</a>
diff --git a/engines/petka/help/res_view.txt b/engines/petka/help/res_view.txt
new file mode 100644
index 000000000..b0ff49f59
--- /dev/null
+++ b/engines/petka/help/res_view.txt
@@ -0,0 +1,3 @@
+Подробная информация о ресурсе
+
+На этой странице можно просмотреть подробную информацию о ресурсе.
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index c45a01d17..72ebd08b3 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -21,7 +21,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2g 2014-05-16"
+VERSION = "v0.2i 2014-05-16"
 
 def hlesc(value):
     if value is None:
@@ -106,6 +106,7 @@ class App(tkinter.Frame):
         self.path_handler = {}
         self.curr_main = -1 # 0 - frame, 1 - canvas
         self.curr_path = []
+        self.curr_help = ""
         self.last_path = [None]
         self.last_fn = ""
         self.curr_mode = 0
@@ -336,6 +337,8 @@ class App(tkinter.Frame):
         else:
             path = loc
         path = tuple(path)
+        while path[-1:] == ("",):
+            path = path[:-1]
 
         if withhist:
             self.hist.append([path])
@@ -343,6 +346,10 @@ class App(tkinter.Frame):
 
         print("DEBUG: Open", path)
         self.curr_path = path
+        if len(path) > 0:
+            self.curr_help = path[0]
+        else:
+            self.curr_help = ""
         if len(path) > 0:
             if path[0] in self.path_handler:
                 return self.path_handler[path[0]](path)
@@ -603,7 +610,7 @@ class App(tkinter.Frame):
                 num = self.curr_lb.curselection()[0]
                 num = int(num)
             except:
-                pass
+                return None
             return num
 
         if self.curr_lb_acts:
@@ -627,10 +634,7 @@ class App(tkinter.Frame):
         self.histf = []
 
     def on_help(self):
-        if len(self.curr_path) > 0:
-            self.open_path(["help", self.curr_path[0]])
-        else:
-            self.open_path(["help"])
+        self.open_path(["help", self.curr_help])
 
     def on_back(self):
         if len(self.hist) > 1:
@@ -821,6 +825,7 @@ class App(tkinter.Frame):
             return self.path_default(path)
 
     def path_res_open(self, pref, res_id, mode):
+        self.curr_help = "res_view"
         if res_id not in self.sim.res:        
             self.switch_view(0)
             self.clear_info()


Commit: ce018979a3cfe9ba9a5347b28bd3a11edd4a2027
    https://github.com/scummvm/scummvm-tools/commit/ce018979a3cfe9ba9a5347b28bd3a11edd4a2027
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: More dialog opcodes information in MENU

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 72ebd08b3..14f139cfe 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1302,44 +1302,68 @@ class App(tkinter.Frame):
                 for didx, dlg in enumerate(act.dlgs):
                     self.add_info("    {}) <i>0x{:X} 0x{:X}</i>, ops: {}\n".\
                         format(didx, dlg.arg1, dlg.arg2, len(dlg.ops)))
+                    # scan for used adreses
+                    usedadr = []
+                    for op in dlg.ops:
+                        if op.opcode == 0x3 or \
+                            op.opcode == 0x4: # GOTO or MENURET
+                            if op.ref not in usedadr:
+                                usedadr.append(op.ref)
+                    if len(usedadr) > 0:
+                        usedadr.append(dlg.op_start)
+                    usedmenu = {}
                     for oidx, op in enumerate(dlg.ops):
                         cmt = ""
                         opref = "0x{:X}".format(op.ref)
                         opcode = fmt_dlgop(op.opcode)
-                        if op.opcode == 0x7:
-                            opcode = "PLAY"
-                            if op.msg:
-                                opref = self.fmt_hl_msg(op.ref)
-                                objref = self.fmt_hl_obj(op.msg.obj.idx)
-                                cmt = " / obj={}, msg={}".\
-                                    format(objref, 
-                                        self.fmt_hl_msg(op.ref, True))
-                        elif op.opcode == 2 or op.opcode == 8: # MENU or CIRCLE
+                        if op.pos in usedadr:
+                            self.add_info("      <i>label_{:X}:</i>\n".format(
+                                op.pos))
+                        if op.opcode == 2 or op.opcode == 8: # MENU or CIRCLE
                             cmt = " / select "
                             doarr = []
                             docurr = []
                             sellen = op.ref % 0x100
                             skiptobrk = False
-                            for op2 in dlg.ops[oidx + 1:]:
+                            menuactstart = None
+                            for oidx2, op2 in enumerate(dlg.ops[oidx + 1:]):
                                 if op2.opcode == 0x1: # BREAK
                                     doarr.append(docurr)
                                     skiptobrk = False
                                     if len(doarr) == sellen:
+                                        menuactstart = oidx2 + oidx + 2
                                         break
                                     docurr = []
                                 elif op2.opcode == 0x7 and not skiptobrk: # PLAY
                                     docurr.append(self.fmt_hl_msg(op2.ref))
                                 else:
                                     docurr = ["complex"]
-                                    
-                                    
                             if len(doarr) < sellen:
                                 cmt = " / {} select broken, required={}, "\
                                     "got={}".format(opcode, sellen, len(doarr))
                             else:
                                 cmt += ",".join(["+".join(x) for x in doarr])
-                        self.add_info("      <i>{:04X}:</i> {} 0x{:X} {}{}\n".\
-                            format(op.pos, opcode, op.arg, opref, cmt))
+                            if menuactstart is not None:
+                                for oidx2, op2 in enumerate(dlg.ops[\
+                                        menuactstart:menuactstart + sellen]):
+                                    usedmenu[op2.pos] = (op.pos, oidx2)
+                        elif op.opcode == 0x3 or \
+                            op.opcode == 0x4: # GOTO or MENURET
+                            opref = "<i>label_{:X}</i>".format(op.ref)
+                            if op.pos in usedmenu:
+                                cmt = " / menu=<i>label_{:X}</i>, case=0x{:}".\
+                                    format(*usedmenu[op.pos])
+                        elif op.opcode == 0x7:
+                            opcode = "PLAY"
+                            if op.msg:
+                                opref = self.fmt_hl_msg(op.ref)
+                                objref = self.fmt_hl_obj(op.msg.obj.idx)
+                                cmt = " / obj={}, msg={}".\
+                                    format(objref, 
+                                        self.fmt_hl_msg(op.ref, True))
+
+                        self.add_info("        {} 0x{:X} {}{}\n".\
+                            format(opcode, op.arg, opref, cmt))
 
             def usedby(lst):
                 for idx, rec in enumerate(lst):
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 137cb24af..61bad62d9 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -72,10 +72,12 @@ OPCODES = {
 
 DLGOPS = {
     1:  ("BREAK",       0),
-    2:  ("MENU",        2),
+    2:  ("MENU",        1),
+    3:  ("GOTO",        3),
+    4:  ("MENURET",     4),
     6:  ("RETURN",      0),
-    7:  ("PLAY",        1),
-    8:  ("CIRCLE",      3),
+    7:  ("PLAY",        5),
+    8:  ("CIRCLE",      6),
 }
 
 class ScrObject:


Commit: 55c1dac47c46c985e3776cf6e68e4f28f073c35a
    https://github.com/scummvm/scummvm-tools/commit/55c1dac47c46c985e3776cf6e68e4f28f073c35a
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: More dialog opcodes information in MENU

Changed paths:
    engines/petka/help/changes.txt


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index d446c5e63..185f60226 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -4,7 +4,7 @@
 2014-05-16 версия 0.2i
 ----------------------
 Доработана справочная система.
-
+Добработан вывод информации о опкодах в диалогах
 
 2014-05-16 версия 0.2g
 ----------------------


Commit: 1caee4daec89ab029b11faeeff7b7dd26eb977f9
    https://github.com/scummvm/scummvm-tools/commit/1caee4daec89ab029b11faeeff7b7dd26eb977f9
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: More dialog opcodes for BREAK

Changed paths:
    engines/petka/help/index.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/index.txt b/engines/petka/help/index.txt
index 265425f56..824919cb6 100644
--- a/engines/petka/help/index.txt
+++ b/engines/petka/help/index.txt
@@ -1,6 +1,6 @@
 Справка
 
-Версия: 0.2e 2014-05-16
+Версия: 0.2i 2014-05-16
 
  * <a href="/help/changes">Что нового</a>
  * <a href="/help/faq">Часто задаваемые вопросы</a>
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 14f139cfe..5be02ec98 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1312,6 +1312,7 @@ class App(tkinter.Frame):
                     if len(usedadr) > 0:
                         usedadr.append(dlg.op_start)
                     usedmenu = {}
+                    usedcase = {}
                     for oidx, op in enumerate(dlg.ops):
                         cmt = ""
                         opref = "0x{:X}".format(op.ref)
@@ -1319,7 +1320,15 @@ class App(tkinter.Frame):
                         if op.pos in usedadr:
                             self.add_info("      <i>label_{:X}:</i>\n".format(
                                 op.pos))
-                        if op.opcode == 2 or op.opcode == 8: # MENU or CIRCLE
+                        if op.opcode == 0x1: # BREAK
+                            if op.pos in usedcase:
+                                if len(usedadr) > 0:
+                                    cmt = " / end select <i>label_{:X}</i>, "\
+                                        "case=0x{:}".format(*usedcase[op.pos])
+                                else:
+                                    cmt = " / end select case=0x{:}".\
+                                        format(usedcase[op.pos][1])
+                        elif op.opcode == 0x2 or op.opcode == 0x8: # MENU or CIRCLE
                             cmt = " / select "
                             doarr = []
                             docurr = []
@@ -1328,9 +1337,10 @@ class App(tkinter.Frame):
                             menuactstart = None
                             for oidx2, op2 in enumerate(dlg.ops[oidx + 1:]):
                                 if op2.opcode == 0x1: # BREAK
+                                    usedcase[op2.pos] = (op.pos, len(doarr))
                                     doarr.append(docurr)
                                     skiptobrk = False
-                                    if len(doarr) == sellen:
+                                    if op.opcode == 0x8 and len(doarr) == sellen:
                                         menuactstart = oidx2 + oidx + 2
                                         break
                                     docurr = []
@@ -1351,8 +1361,8 @@ class App(tkinter.Frame):
                             op.opcode == 0x4: # GOTO or MENURET
                             opref = "<i>label_{:X}</i>".format(op.ref)
                             if op.pos in usedmenu:
-                                cmt = " / menu=<i>label_{:X}</i>, case=0x{:}".\
-                                    format(*usedmenu[op.pos])
+                                cmt = " / action menu=<i>label_{:X}</i>, "\
+                                    "case=0x{:}".format(*usedmenu[op.pos])
                         elif op.opcode == 0x7:
                             opcode = "PLAY"
                             if op.msg:


Commit: 86b69588ea7fd26a05b6f987385e0d112bdd9b81
    https://github.com/scummvm/scummvm-tools/commit/86b69588ea7fd26a05b6f987385e0d112bdd9b81
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fix

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 5be02ec98..c6f5f535e 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1340,8 +1340,9 @@ class App(tkinter.Frame):
                                     usedcase[op2.pos] = (op.pos, len(doarr))
                                     doarr.append(docurr)
                                     skiptobrk = False
-                                    if op.opcode == 0x8 and len(doarr) == sellen:
-                                        menuactstart = oidx2 + oidx + 2
+                                    if len(doarr) == sellen:
+                                        if op.opcode == 0x2:
+                                            menuactstart = oidx2 + oidx + 2
                                         break
                                     docurr = []
                                 elif op2.opcode == 0x7 and not skiptobrk: # PLAY


Commit: 45ef71d4bb5d000dee511a0e6ac04478c7cdfbd4
    https://github.com/scummvm/scummvm-tools/commit/45ef71d4bb5d000dee511a0e6ac04478c7cdfbd4
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Decompile highlighting

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 185f60226..6479be033 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -5,6 +5,7 @@
 ----------------------
 Доработана справочная система.
 Добработан вывод информации о опкодах в диалогах
+Доработана подсветка комментариев
 
 2014-05-16 версия 0.2g
 ----------------------
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index c6f5f535e..986161ca9 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -29,14 +29,18 @@ def hlesc(value):
     return value.replace("\\", "\\\\").replace("<", "\\<").replace(">", "\\>")
 
 def fmt_opcode(opcode):
-    return petka.OPCODES.get(opcode, ["OP{:04X}".format(opcode)])[0]
+    return petka.OPCODES.get(opcode, ["<font color=\"red\">OP{:04X}</font>".\
+        format(opcode)])[0]
 
 def fmt_dlgop(opcode):
-    return petka.DLGOPS.get(opcode, ["OP{:02X}".format(opcode)])[0]
+    return petka.DLGOPS.get(opcode, ["<font color=\"red\">OP{:02X}</font>".\
+        format(opcode)])[0]
 
 def fmt_hl(loc, desc):
     return "<a href=\"{}\">{}</a>".format(loc, desc)
 
+def fmt_cmt(cmt):
+    return "<font color=\"#606060\">{}</font>".format(cmt)
 
 # thanx to http://effbot.org/zone/tkinter-text-hyperlink.htm
 class HyperlinkManager:
@@ -57,6 +61,7 @@ class HyperlinkManager:
 
     def reset(self):
     	self.links = {}
+    	self.colors = []
 
     def add(self, action):
         # add an action to the manager.  returns tags to use in
@@ -65,6 +70,14 @@ class HyperlinkManager:
         self.links[tag] = action
         return "hyper", tag
 
+    def color(self, color):
+        tag = "color-{}".format(color)
+        if tag not in self.colors:
+            self.colors.append(tag)
+            self.text.tag_config(tag, foreground = color)
+            self.text.tag_raise("hyper")
+        return (tag,)
+
     def _enter(self, event):
         self.text.config(cursor = "hand2")
 
@@ -553,7 +566,7 @@ class App(tkinter.Frame):
                 if ch == ">":
                     if len(curr_text) > 0:                    
                         self.text_view.insert(tkinter.INSERT, curr_text, \
-                            tuple([x for x in tags for x in x]))
+                            tuple(reversed([x for x in tags for x in x])))
                     if curr_tag[:7] == "a href=":
                         ref = curr_tag[7:]
                         if ref[:1] == "\"":
@@ -565,6 +578,13 @@ class App(tkinter.Frame):
                                 return self.open_path(path)
                             return cb
                         tags.append(self.text_hl.add(make_cb(ref)))
+                    elif curr_tag[:11] == "font color=":
+                        ref = curr_tag[11:]
+                        if ref[:1] == "\"":
+                            ref = ref[1:]
+                        if ref[-1:] == "\"":
+                            ref = ref[:-1]
+                        tags.append(self.text_hl.color(ref))
                     elif curr_tag == "b":
                         tags.append(["bold"])
                     elif curr_tag == "i":
@@ -579,7 +599,7 @@ class App(tkinter.Frame):
                     curr_tag += ch
         if len(curr_text) > 0: 
             self.text_view.insert(tkinter.INSERT, curr_text, \
-                tuple([x for x in tags for x in x]))
+                tuple(reversed([x for x in tags for x in x])))
         
     def insert_lb_act(self, name, act, key = None):
         if key is not None:
@@ -1074,7 +1094,8 @@ class App(tkinter.Frame):
                             msg += "-1"
                         else:
                             msg += "0x{:X}".format(arg)
-                    self.add_info(msg + " / {}\n".format(hlesc(ref[0].name)))
+                    self.add_info(msg + fmt_cmt(" // " + self.fmt_hl_obj(
+                        ref[0].idx, True)) + "\n")
 
             resused = []
             dlgused = []
@@ -1087,7 +1108,8 @@ class App(tkinter.Frame):
                         act_ref = "THIS"
                     else:
                         if act_ref in self.sim.obj_idx:
-                            cmt = " / " + self.fmt_hl_obj(act_ref, True)
+                            cmt = fmt_cmt(" // " + self.fmt_hl_obj(act_ref, 
+                                True))
                             act_ref = self.fmt_hl_obj(act_ref)
                         else:
                             act_ref = "0x{:X}".format(act_ref)
@@ -1101,7 +1123,8 @@ class App(tkinter.Frame):
                         self.add_info("THIS")
                     else:
                         self.add_info(self.fmt_hl_obj_scene(op[0]))
-                        cmt = " / " + self.fmt_hl_obj_scene(op[0], True)
+                        cmt = fmt_cmt(" // " + self.fmt_hl_obj_scene(op[0], 
+                            True))
                     msg = ""
                     if op[2] != 0xffff:
                         if op[2] not in resused and op[2] in self.sim.res:
@@ -1296,9 +1319,10 @@ class App(tkinter.Frame):
             self.add_info("<b>Dialog handlers<b>: {}\n".format(len(grp.acts)))
             for idx, act in enumerate(grp.acts):
                 self.add_info("  {}) <u>on {} {} 0x{:X} 0x{:X}</u>, dlgs: "\
-                    "{} / {}\n".format(idx, fmt_opcode(act.opcode), 
+                    "{}{}\n".format(idx, fmt_opcode(act.opcode), 
                         self.fmt_hl_obj(act.ref), act.arg1, act.arg2, \
-                        len(act.dlgs), self.fmt_hl_obj(act.ref, True)))
+                        len(act.dlgs), fmt_cmt(" // " + 
+                            self.fmt_hl_obj(act.ref, True))))
                 for didx, dlg in enumerate(act.dlgs):
                     self.add_info("    {}) <i>0x{:X} 0x{:X}</i>, ops: {}\n".\
                         format(didx, dlg.arg1, dlg.arg2, len(dlg.ops)))
@@ -1323,11 +1347,13 @@ class App(tkinter.Frame):
                         if op.opcode == 0x1: # BREAK
                             if op.pos in usedcase:
                                 if len(usedadr) > 0:
-                                    cmt = " / end select <i>label_{:X}</i>, "\
-                                        "case=0x{:}".format(*usedcase[op.pos])
+                                    cmt = fmt_cmt(" // end select <i>"\
+                                        "label_{:X}</i>, case=0x{:}"\
+                                        "".format(*usedcase[op.pos]))
                                 else:
-                                    cmt = " / end select case=0x{:}".\
-                                        format(usedcase[op.pos][1])
+                                    cmt = fmt_cmt(" // end "\
+                                        "select case=0x{:}".\
+                                        format(usedcase[op.pos][1]))
                         elif op.opcode == 0x2 or op.opcode == 0x8: # MENU or CIRCLE
                             cmt = " / select "
                             doarr = []
@@ -1350,10 +1376,12 @@ class App(tkinter.Frame):
                                 else:
                                     docurr = ["complex"]
                             if len(doarr) < sellen:
-                                cmt = " / {} select broken, required={}, "\
-                                    "got={}".format(opcode, sellen, len(doarr))
+                                cmt = fmt_cmt(" // {} select broken, "\
+                                    "required={}, got={}".\
+                                    format(opcode, sellen, len(doarr)))
                             else:
                                 cmt += ",".join(["+".join(x) for x in doarr])
+                            cmt = fmt_cmt(cmt)
                             if menuactstart is not None:
                                 for oidx2, op2 in enumerate(dlg.ops[\
                                         menuactstart:menuactstart + sellen]):
@@ -1362,19 +1390,25 @@ class App(tkinter.Frame):
                             op.opcode == 0x4: # GOTO or MENURET
                             opref = "<i>label_{:X}</i>".format(op.ref)
                             if op.pos in usedmenu:
-                                cmt = " / action menu=<i>label_{:X}</i>, "\
-                                    "case=0x{:}".format(*usedmenu[op.pos])
+                                cmt = fmt_cmt(" // action menu=<i>"\
+                                    "label_{:X}</i>, case=0x{:}".\
+                                    format(*usedmenu[op.pos]))
                         elif op.opcode == 0x7:
                             opcode = "PLAY"
                             if op.msg:
                                 opref = self.fmt_hl_msg(op.ref)
                                 objref = self.fmt_hl_obj(op.msg.obj.idx)
-                                cmt = " / obj={}, msg={}".\
+                                cmt = fmt_cmt(" // obj={}, msg={}".\
                                     format(objref, 
-                                        self.fmt_hl_msg(op.ref, True))
-
-                        self.add_info("        {} 0x{:X} {}{}\n".\
-                            format(opcode, op.arg, opref, cmt))
+                                        self.fmt_hl_msg(op.ref, True)))
+                        
+                        oparg = " 0x{:X} ".format(op.arg)
+                        if (op.opcode == 0x1 or op.opcode == 0x6) and \
+                                op.arg == 0 and op.ref == 0:
+                            oparg = ""
+                            opref = ""
+                        self.add_info("        {}{}{}{}\n".\
+                            format(opcode, oparg, opref, cmt))
 
             def usedby(lst):
                 for idx, rec in enumerate(lst):


Commit: adf0fe0f22a40f79cd87da671d8a55b8dc007658
    https://github.com/scummvm/scummvm-tools/commit/adf0fe0f22a40f79cd87da671d8a55b8dc007658
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Display cast colors

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py
    engines/petka/petka/fman.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 986161ca9..59ca956f5 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -40,7 +40,7 @@ def fmt_hl(loc, desc):
     return "<a href=\"{}\">{}</a>".format(loc, desc)
 
 def fmt_cmt(cmt):
-    return "<font color=\"#606060\">{}</font>".format(cmt)
+    return "<font color=\"#4d4d4d\">{}</font>".format(cmt)
 
 # thanx to http://effbot.org/zone/tkinter-text-hyperlink.htm
 class HyperlinkManager:
@@ -62,6 +62,7 @@ class HyperlinkManager:
     def reset(self):
     	self.links = {}
     	self.colors = []
+    	self.bgs = []
 
     def add(self, action):
         # add an action to the manager.  returns tags to use in
@@ -78,6 +79,14 @@ class HyperlinkManager:
             self.text.tag_raise("hyper")
         return (tag,)
 
+    def bg(self, color):
+        tag = "bg-{}".format(color)
+        if tag not in self.bgs:
+            self.bgs.append(tag)
+            self.text.tag_config(tag, background = color)
+            self.text.tag_raise("hyper")
+        return (tag,)
+
     def _enter(self, event):
         self.text.config(cursor = "hand2")
 
@@ -585,6 +594,13 @@ class App(tkinter.Frame):
                         if ref[-1:] == "\"":
                             ref = ref[:-1]
                         tags.append(self.text_hl.color(ref))
+                    elif curr_tag[:8] == "font bg=":
+                        ref = curr_tag[8:]
+                        if ref[:1] == "\"":
+                            ref = ref[1:]
+                        if ref[-1:] == "\"":
+                            ref = ref[:-1]
+                        tags.append(self.text_hl.bg(ref))
                     elif curr_tag == "b":
                         tags.append(["bold"])
                     elif curr_tag == "i":
@@ -705,6 +721,12 @@ class App(tkinter.Frame):
                 return "/invntr/{}".format(inv_id)
         return "/no_invntr/{}".format(key)
 
+    def find_path_cast(self, key):
+        for cast_id, name in enumerate(self.sim.castsord):
+            if name == key:
+                return "/casts/{}".format(cast_id)
+        return "/no_casts/{}".format(key)
+
     def fmt_hl_msg(self, msg_id, full = False):
         msg_idx = {}
         if msg_id < len(self.sim.msgs):
@@ -1066,6 +1088,19 @@ class App(tkinter.Frame):
                 self.add_info("  " + fmt_hl(self.find_path_invntr(rec.name), 
                     "Invntr") + ": {}\n".format(
                         hlesc(self.sim.invntr[rec.name])))
+            if rec.cast:
+                bg = 0
+                r = rec.cast[0] 
+                g = rec.cast[1]
+                b = rec.cast[2]
+                if (r + g * 2 + b) // 3 < 160: 
+                    bg = 255
+                self.add_info("  " + fmt_hl(self.find_path_cast(rec.name), 
+                    "Cast") + ":   <font bg=\"#{bg:02x}{bg:02x}{bg:02x}\">"
+                    "<font color=\"#{r:02x}{g:02x}{b:02x}\">"\
+                    "<b> #{r:02x}{g:02x}{b:02x} </b></font></font>\n".\
+                    format(bg = bg, r = r, g = g, b = b))
+            
             # references / backreferences                    
             if isobj:
                 # search where object used
@@ -1224,7 +1259,19 @@ class App(tkinter.Frame):
             return self.path_default([])
         def info(name):
             self.add_info("<b>Cast</b>: {}\n".format(hlesc(name)))
-            self.add_info("Value: {}\n\n".format(self.sim.casts[name]))
+            self.add_info("Value: {}\n".format(self.sim.casts[name]))
+            try:
+                val = self.sim.casts[name].split(" ")
+                val = [x for x in val if x]
+                r = int(val[0])
+                g = int(val[1])
+                b = int(val[2])
+            except:
+                r, g, b = 0, 0, 0
+            hl = "<font bg=\"#{:02x}{:02x}{:02x}\">        </font>".\
+                format(r, g, b)
+            self.add_info("  " + hl + "\n")
+            self.add_info("  " + hl + "\n\n")
             # search for objects
             self.add_info("<b>Applied for</b>:\n")
             for idx, obj in enumerate(self.sim.objects):
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 61bad62d9..3dd31ef1f 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -85,6 +85,7 @@ class ScrObject:
         self.idx = idx
         self.name = name
         self.acts = None
+        self.cast = None
 
 class MsgObject:
     def __init__(self, idx, wav, arg1, arg2, arg3):
@@ -366,10 +367,20 @@ class Engine:
             self.casts = ini["all"]
             self.castsord = ini["__order__"]["all"]
             f.close()
+
         # bind casts to objects
-        for cast in self.castsord:
-            #print(cast)
-            pass
+        for obj in self.objects:
+            if obj.name in self.casts:
+                # parse color
+                try:
+                    val = self.casts[obj.name].split(" ")
+                    val = [x for x in val if x]
+                    r = int(val[0])
+                    g = int(val[1])
+                    b = int(val[2])
+                except:
+                    r, g, b = 255, 255, 255
+                obj.cast = (r, g, b)
             
     def load_dialogs(self):
         self.msgs = []
diff --git a/engines/petka/petka/fman.py b/engines/petka/petka/fman.py
index 142597b92..b14ad3eea 100644
--- a/engines/petka/petka/fman.py
+++ b/engines/petka/petka/fman.py
@@ -63,7 +63,7 @@ class FileManager:
                 self.strtable[fname] = (len(self.strfd),) + index_table[idx]
             else:
                 if len(fname) > 0:
-                    raise EngineError("Extra file record \"{}\" in \"{}\"".\
+                    print("DEBUG:Extra file record \"{}\" in \"{}\"".\
                         format(fname, name))
         # add file descriptor
         self.strfd.append((f, name, tag))


Commit: 83c51d77a6d99917b581c78f1259d0432f4d674f
    https://github.com/scummvm/scummvm-tools/commit/83c51d77a6d99917b581c78f1259d0432f4d674f
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Show where object used in TALK opcode

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 6479be033..3918a1c3e 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,6 +1,10 @@
 Что нового
 ==========
 
+2014-05-16 версия 0.2j
+----------------------
+Отображаемые цвета изображаются нужным цветом
+
 2014-05-16 версия 0.2i
 ----------------------
 Доработана справочная система.
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 59ca956f5..58bc1b31a 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -21,7 +21,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2i 2014-05-16"
+VERSION = "v0.2j 2014-05-16"
 
 def hlesc(value):
     if value is None:
@@ -1189,13 +1189,35 @@ class App(tkinter.Frame):
                     format(len(dlgused)))
                 for grp_id in dlgused:
                     self.add_info("  " + self.fmt_hl_dlg(grp_id, True)+ "\n")
-            
+
+            # messages used by this object            
             if isobj:
-                self.add_info("\n<b>Messages</b>:\n")
+                wasmsg = False
                 for msg in self.sim.msgs:
                     if msg.obj.idx != rec.idx: continue
+                    if not wasmsg:
+                        self.add_info("\n<b>Messages</b>:\n")
+                        wasmsg = True
                     self.add_info("  " + self.fmt_hl_msg(msg.idx, True) + "\n")
 
+            # objects tan use this objects in TALK opcode
+            wasmsg = False
+            for obj2 in self.sim.objects:
+                for idx, (act_op, act_status, act_ref, ops) in \
+                        enumerate(obj2.acts):
+                    for oidx, op in enumerate(ops):
+                        if op[1] == 0xA and op[0] == rec.idx: # TALK
+                            if not wasmsg:
+                                self.add_info("\n<b>Used in TALK</b>:\n")
+                                wasmsg = True
+                            self.add_info("  " + self.fmt_hl_obj(obj2.idx) + 
+                              " on " + fmt_opcode(act_op) + 
+                              " #{}".format(idx) + fmt_cmt(" // " + 
+                              self.fmt_hl_obj(obj2.idx, True)) + "\n")
+                            #print(op)
+                            #print()
+            
+
     def path_std_items(self, path, level, guiname, guiitem, lst, lst_idx, 
             lbmode, cb):
         self.switch_view(0)
@@ -1402,7 +1424,7 @@ class App(tkinter.Frame):
                                         "select case=0x{:}".\
                                         format(usedcase[op.pos][1]))
                         elif op.opcode == 0x2 or op.opcode == 0x8: # MENU or CIRCLE
-                            cmt = " / select "
+                            cmt = " // select "
                             doarr = []
                             docurr = []
                             sellen = op.ref % 0x100


Commit: 611c55928c08aa2ef4a85bcd467cbe9959fdaf50
    https://github.com/scummvm/scummvm-tools/commit/611c55928c08aa2ef4a85bcd467cbe9959fdaf50
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Show where object used in TALK opcode

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 3918a1c3e..a8a7f40f2 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,6 +1,10 @@
 Что нового
 ==========
 
+2014-05-16 версия 0.2k
+----------------------
+Отображает информацию где используется объект в опкоде TALK
+
 2014-05-16 версия 0.2j
 ----------------------
 Отображаемые цвета изображаются нужным цветом
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 58bc1b31a..c163fca27 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -21,7 +21,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2j 2014-05-16"
+VERSION = "v0.2k 2014-05-16"
 
 def hlesc(value):
     if value is None:


Commit: 32232789613ee13cbb095fcf59e9be79e7548aae
    https://github.com/scummvm/scummvm-tools/commit/32232789613ee13cbb095fcf59e9be79e7548aae
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Show where object used in all opcodes

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index a8a7f40f2..ae18f81a1 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -3,7 +3,8 @@
 
 2014-05-16 версия 0.2k
 ----------------------
-Отображает информацию где используется объект в опкоде TALK
+Отображает информацию где используется объект с указанием опкодов и 
+  обработчиков
 
 2014-05-16 версия 0.2j
 ----------------------
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index c163fca27..bc3151277 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1200,23 +1200,30 @@ class App(tkinter.Frame):
                         wasmsg = True
                     self.add_info("  " + self.fmt_hl_msg(msg.idx, True) + "\n")
 
+            oplst = {}
             # objects tan use this objects in TALK opcode
             wasmsg = False
             for obj2 in self.sim.objects:
+                if obj2.idx == rec.idx: continue
                 for idx, (act_op, act_status, act_ref, ops) in \
                         enumerate(obj2.acts):
                     for oidx, op in enumerate(ops):
-                        if op[1] == 0xA and op[0] == rec.idx: # TALK
-                            if not wasmsg:
-                                self.add_info("\n<b>Used in TALK</b>:\n")
-                                wasmsg = True
-                            self.add_info("  " + self.fmt_hl_obj(obj2.idx) + 
-                              " on " + fmt_opcode(act_op) + 
-                              " #{}".format(idx) + fmt_cmt(" // " + 
-                              self.fmt_hl_obj(obj2.idx, True)) + "\n")
-                            #print(op)
-                            #print()
-            
+                        if op[0] == rec.idx:
+                            arr = oplst.get(op[1], [])
+                            arr.append((obj2.idx, act_op, idx))
+                            oplst[op[1]] = arr
+                            break
+
+            klst = list(petka.OPCODES.keys())
+            klst.sort()
+            for k in klst:
+                if k not in oplst: continue
+                self.add_info("\n<b>Used in " + fmt_opcode(k) + "</b>:\n")
+                for oid, htp, hid in oplst[k]:
+                    self.add_info("  " + self.fmt_hl_obj(oid) + 
+                      " on " + fmt_opcode(htp) + 
+                      " #{}".format(hid) + fmt_cmt(" // " + 
+                      self.fmt_hl_obj(oid, True)) + "\n")
 
     def path_std_items(self, path, level, guiname, guiitem, lst, lst_idx, 
             lbmode, cb):


Commit: c32cf831b5519c8a32b629ebe0215c8106033243
    https://github.com/scummvm/scummvm-tools/commit/c32cf831b5519c8a32b629ebe0215c8106033243
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: search opcodes refs in scenes too

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index bc3151277..2ef010c9d 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1203,7 +1203,7 @@ class App(tkinter.Frame):
             oplst = {}
             # objects tan use this objects in TALK opcode
             wasmsg = False
-            for obj2 in self.sim.objects:
+            for obj2 in self.sim.objects + self.sim.scenes:
                 if obj2.idx == rec.idx: continue
                 for idx, (act_op, act_status, act_ref, ops) in \
                         enumerate(obj2.acts):
@@ -1220,10 +1220,10 @@ class App(tkinter.Frame):
                 if k not in oplst: continue
                 self.add_info("\n<b>Used in " + fmt_opcode(k) + "</b>:\n")
                 for oid, htp, hid in oplst[k]:
-                    self.add_info("  " + self.fmt_hl_obj(oid) + 
+                    self.add_info("  " + self.fmt_hl_obj_scene(oid) + 
                       " on " + fmt_opcode(htp) + 
                       " #{}".format(hid) + fmt_cmt(" // " + 
-                      self.fmt_hl_obj(oid, True)) + "\n")
+                      self.fmt_hl_obj_scene(oid, True)) + "\n")
 
     def path_std_items(self, path, level, guiname, guiitem, lst, lst_idx, 
             lbmode, cb):


Commit: 5c26722f94d2e9f3cac5e4750bc50dccf7e2ec44
    https://github.com/scummvm/scummvm-tools/commit/5c26722f94d2e9f3cac5e4750bc50dccf7e2ec44
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: test export .pot files

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 2ef010c9d..05e7dc253 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1514,12 +1514,40 @@ class App(tkinter.Frame):
         if path[1] == "image":
             self.switch_view(1)
             self.main_image = tkinter.PhotoImage(file = "img/splash.gif")
-        else:
+        elif path[1] == "info":
             self.switch_view(0)
             self.clear_info()
             self.add_info("Information panel for {}\n".format(path))
             for i in range(100):
                 self.add_info("  Item {}\n".format(i))
+        elif path[1] == "translate":
+            self.switch_view(0)
+            self.clear_info()
+            self.add_info("Save translation template\n    ")
+            def savepot():
+                f = open("save_{}.pot".format(self.sim.curr_part), "wb")
+                try:
+                    def saveitem(msgid, msgstr, msgctx):
+                        if msgctx:
+                            f.write("msgctx \"{}\"\n".format(hlesc(msgctx)).\
+                                encode("UTF-8"))
+                        f.write("msgid \"{}\"\n".format(hlesc(msgid)).\
+                            encode("UTF-8"))
+                        f.write("msgstr \"{}\"\n\n".format(hlesc(msgstr)).\
+                            encode("UTF-8"))
+                    # objects
+                    for obj in self.sim.objects:
+                        saveitem(obj.name, obj.name, "obj_{}".format(obj.idx))
+                    for scn in self.sim.scenes:
+                        saveitem(scn.name, scn.name, "scene_{}".format(scn.idx))
+                    
+                finally:
+                    f.close()
+            
+            btn = ttk.Button(self.text_view, text = "Save .POT", \
+                command = savepot)
+            self.text_view.window_create(tkinter.INSERT, window = btn)
+            
 
     def path_about(self, path):
         self.switch_view(0)


Commit: 91b4caf9ce1dca7830a8699c9687001e1e63dfc2
    https://github.com/scummvm/scummvm-tools/commit/91b4caf9ce1dca7830a8699c9687001e1e63dfc2
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Translation tools

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/help/cmdline.txt
    engines/petka/help/index.txt
    engines/petka/help/list
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index ae18f81a1..b67406c46 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,6 +1,13 @@
 Что нового
 ==========
 
+2014-05-20 версия 0.2l
+----------------------
+Добавлена функция создания шаблона для перевода (.pot)
+Добавлена загрузка перевода (.po)
+Изменены все разделы где может отображаться переведённа информация
+Изменен порядок загрузкт данных из командной строки
+
 2014-05-16 версия 0.2k
 ----------------------
 Отображает информацию где используется объект с указанием опкодов и 
diff --git a/engines/petka/help/cmdline.txt b/engines/petka/help/cmdline.txt
index a0cae8862..9e578f0dd 100644
--- a/engines/petka/help/cmdline.txt
+++ b/engines/petka/help/cmdline.txt
@@ -2,7 +2,7 @@
 
 Синтаксис вызова программы из коммандной строки
 
-  p12simtran [путь к данным [открываемые разделы]
+  p12explore [-d путь к данным]|[-t путь к переводу]|[открываемый раздел]
 
 Путь к данным - путь к файлу или каталогу где находится файл с данными.
   
@@ -17,5 +17,13 @@
  * /msgs - сообщения
  * /dlgs - группы диалогов
  
- 
+Так как после открытия данных данные о переводе удаляются то рекомендуется
+  следующий порядок загрузки:
+  
+  p12explore -d . /parts/1.0 -t translate/part_1-en_EN.po /msgs/2492
+  
+  * Загруаются данные из текущего каталога
+  * Открывается часть 1, глава 0
+  * Загружается перевод из файла
+  * Открывается сообщение №2492
 
diff --git a/engines/petka/help/index.txt b/engines/petka/help/index.txt
index 824919cb6..28af58a6e 100644
--- a/engines/petka/help/index.txt
+++ b/engines/petka/help/index.txt
@@ -1,6 +1,6 @@
 Справка
 
-Версия: 0.2i 2014-05-16
+Версия: 0.2l 2014-05-20
 
  * <a href="/help/changes">Что нового</a>
  * <a href="/help/faq">Часто задаваемые вопросы</a>
@@ -19,6 +19,7 @@
 
 Прочая информация
 
+ * <a href="/help/translate">Для переводчика</a>
  * <a href="/help/cmdline">Аргументы коммандной строки</a>
  * <a href="/help/support">Информация для подержки</a>
  * <a href="/help/info">Справочники</a>
diff --git a/engines/petka/help/list b/engines/petka/help/list
index a43ec9fd3..984865268 100644
--- a/engines/petka/help/list
+++ b/engines/petka/help/list
@@ -10,6 +10,7 @@ invntr
 casts
 msgs
 dlgs
+translate
 cmdline
 support
 info
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 05e7dc253..ddb8103e9 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -9,6 +9,7 @@ from tkinter import ttk, font, filedialog, messagebox
 from idlelib.WidgetRedirector import WidgetRedirector
 import traceback
 
+# Image processing
 try:
     from PIL import Image
 except ImportError:
@@ -18,16 +19,25 @@ try:
 except ImportError:
     ImageTk = None
 
+# Translations
+try:
+    import polib
+except ImportError:
+    polib = None
+
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2k 2014-05-16"
+VERSION = "v0.2l 2014-05-20"
 
 def hlesc(value):
     if value is None:
         return "None"
     return value.replace("\\", "\\\\").replace("<", "\\<").replace(">", "\\>")
 
+def cesc(value):
+    return value.replace("\\", "\\\\").replace("\"", "\\\"")
+
 def fmt_opcode(opcode):
     return petka.OPCODES.get(opcode, ["<font color=\"red\">OP{:04X}</font>".\
         format(opcode)])[0]
@@ -142,6 +152,9 @@ class App(tkinter.Frame):
         self.need_update = False
         self.canv_view_fact = 1
         self.main_image = tkinter.PhotoImage(width = 1, height = 1)
+        # translation
+        self.tran = None
+        # add on_load handler
         self.after_idle(self.on_first_display)
         
     def create_widgets(self):
@@ -230,6 +243,8 @@ class App(tkinter.Frame):
             if cmd == "load":
                 self.open_data_from(arg)
                 repath = "/"
+            elif cmd == "tran":
+                self.open_tran_from(arg)
             elif cmd == "open":
                 self.open_path(arg)
                 repath = ""
@@ -301,6 +316,17 @@ class App(tkinter.Frame):
                 command = lambda: self.open_path("/hist"),
                 label = "History")
 
+        if polib:
+            menutran = tkinter.Menu(self.menubar, tearoff = 0)
+            self.menubar.add_cascade(menu = menutran,
+                    label = "Translation")
+            menutran.add_command(
+                    command = self.on_tran_save,
+                    label = "Save template (.pot)")
+            menutran.add_command(
+                    command = self.on_tran_load,
+                    label = "Load translation (.po)")
+
         self.menuhelp = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menuhelp,
                 label = "Help")
@@ -686,28 +712,38 @@ class App(tkinter.Frame):
             self.hist.append(np)
             self.open_path(np[0], False)
 
-    def fmt_hl_rec(self, lst_idx, pref, rec_id, full = False):
+    def _t(self, value, tp):
+        if not self.tran: return value
+        if tp in self.tran:
+            if value in self.tran[tp]:
+                return self.tran[tp][value]
+        if value in self.tran["_"]:
+            return self.tran["_"][value]
+        return value
+
+    def fmt_hl_rec(self, lst_idx, pref, rec_id, full, tt):
         if rec_id in lst_idx:
             fmt = fmt_hl("/{}/{}".format(pref, rec_id), str(rec_id))
             if full:
                 try:
                     fmt += " (0x{:X}) - {}".format(rec_id, 
-                        hlesc(lst_idx[rec_id].name))
+                        hlesc(self._t(lst_idx[rec_id].name, tt)))
                 except:
                     fmt += " (0x{:X})".format(rec_id)
             return fmt
         return "{} (0x{:X})".format(rec_id, rec_id)
         
     def fmt_hl_obj(self, obj_id, full = False):
-        return self.fmt_hl_rec(self.sim.obj_idx, "objs", obj_id, full)
+        return self.fmt_hl_rec(self.sim.obj_idx, "objs", obj_id, full, "obj")
         
     def fmt_hl_scene(self, scn_id, full = False):
-        return self.fmt_hl_rec(self.sim.scn_idx, "scenes", scn_id, full)
+        return self.fmt_hl_rec(self.sim.scn_idx, "scenes", scn_id, full, "scn")
 
     def fmt_hl_obj_scene(self, rec_id, full = False):
         if rec_id in self.sim.obj_idx:
-            return self.fmt_hl_rec(self.sim.obj_idx, "objs", rec_id, full)
-        return self.fmt_hl_rec(self.sim.scn_idx, "scenes", rec_id, full)
+            return self.fmt_hl_rec(self.sim.obj_idx, "objs", rec_id,
+                full, "obj")
+        return self.fmt_hl_rec(self.sim.scn_idx, "scenes", rec_id, full, "scn")
         
     def find_path_name(self, key):
         for name_id, name in enumerate(self.sim.namesord):
@@ -731,10 +767,10 @@ class App(tkinter.Frame):
         msg_idx = {}
         if msg_id < len(self.sim.msgs):
             msg_idx[msg_id] = self.sim.msgs[msg_id]
-        return self.fmt_hl_rec(msg_idx, "msgs", msg_id, full)
+        return self.fmt_hl_rec(msg_idx, "msgs", msg_id, full, "msg")
 
     def fmt_hl_dlg(self, grp_id, full = False):
-        return self.fmt_hl_rec(self.sim.dlg_idx, "dlgs", grp_id, full)
+        return self.fmt_hl_rec(self.sim.dlg_idx, "dlgs", grp_id, full, "dlg")
 
     def path_info_outline(self):
         if self.sim is None:
@@ -1054,7 +1090,8 @@ class App(tkinter.Frame):
             else:
                 self.update_gui("Scenes ({})".format(len(lst)))
             for rec in lst:
-                self.insert_lb_act("{} - {}".format(rec.idx, rec.name), \
+                self.insert_lb_act("{} - {}".format(rec.idx, 
+                    self._t(rec.name, "obj" if isobj else "scn")),\
                     [self.curr_path[0], rec.idx], rec.idx)
         # change                
         rec = None
@@ -1078,16 +1115,28 @@ class App(tkinter.Frame):
             # record info
             self.add_info(("<b>Object</b>" if isobj \
                 else "<b>Scene</b>") + ":\n")
-            self.add_info("  Index:  {} (0x{:X})\n  Name:   {}\n".\
-                format(rec.idx, rec.idx, hlesc(rec.name)))
-            if rec.name in self.sim.names:
-                self.add_info("  " + fmt_hl(self.find_path_name(rec.name), 
-                    "Alias") + ":  {}\n".format(
-                        hlesc(self.sim.names[rec.name])))
-            if rec.name in self.sim.invntr:
-                self.add_info("  " + fmt_hl(self.find_path_invntr(rec.name), 
-                    "Invntr") + ": {}\n".format(
-                        hlesc(self.sim.invntr[rec.name])))
+            self.add_info("  Index:     {} (0x{:X})\n".format(rec.idx, rec.idx))
+            self.add_info("  Name:      {}\n".format(hlesc(rec.name)))
+            if self.tran:
+                self.add_info("  Name(t):   {}\n".\
+                    format(hlesc(self._t(rec.name, "obj" if isobj else "scn"))))
+                if rec.name in self.sim.names:
+                    self.add_info("  " + fmt_hl(self.find_path_name(rec.name), 
+                        "Alias") + "(t):  {}\n".format(
+                            hlesc(self._t(self.sim.names[rec.name], "obj"))))
+                if rec.name in self.sim.invntr:
+                    self.add_info("  " + fmt_hl(self.find_path_invntr(rec.name), 
+                        "Invntr") + "(t): {}\n".format(
+                            hlesc(self._t(self.sim.invntr[rec.name], "inv"))))
+            else:
+                if rec.name in self.sim.names:
+                    self.add_info("  " + fmt_hl(self.find_path_name(rec.name), 
+                        "Alias") + ":     {}\n".format(
+                            hlesc(self.sim.names[rec.name])))
+                if rec.name in self.sim.invntr:
+                    self.add_info("  " + fmt_hl(self.find_path_invntr(rec.name), 
+                        "Invntr") + ":    {}\n".format(
+                            hlesc(self.sim.invntr[rec.name])))
             if rec.cast:
                 bg = 0
                 r = rec.cast[0] 
@@ -1096,7 +1145,7 @@ class App(tkinter.Frame):
                 if (r + g * 2 + b) // 3 < 160: 
                     bg = 255
                 self.add_info("  " + fmt_hl(self.find_path_cast(rec.name), 
-                    "Cast") + ":   <font bg=\"#{bg:02x}{bg:02x}{bg:02x}\">"
+                    "Cast") + ":      <font bg=\"#{bg:02x}{bg:02x}{bg:02x}\">"
                     "<font color=\"#{r:02x}{g:02x}{b:02x}\">"\
                     "<b> #{r:02x}{g:02x}{b:02x} </b></font></font>\n".\
                     format(bg = bg, r = r, g = g, b = b))
@@ -1225,15 +1274,16 @@ class App(tkinter.Frame):
                       " #{}".format(hid) + fmt_cmt(" // " + 
                       self.fmt_hl_obj_scene(oid, True)) + "\n")
 
-    def path_std_items(self, path, level, guiname, guiitem, lst, lst_idx, 
+    def path_std_items(self, path, level, guiname, guiitem, tt, lst, lst_idx, 
             lbmode, cb):
         self.switch_view(0)
         if self.last_path[:level] != path[:level]:
             self.update_gui("{} ({})".format(guiname, len(lst)))
             for idx, name in enumerate(lst_idx):
-                lb = name
+                lb = self._t(name, tt)
                 if lbmode == 1:
-                    lb = "{} - {}".format(name, lst[name])
+                    print(name, )
+                    lb = "{} - {}".format(name, self._t(lst[name], tt))
                 self.insert_lb_act(lb, path[:level] + tuple([idx]), idx)
         # change
         name = None
@@ -1258,29 +1308,41 @@ class App(tkinter.Frame):
         if self.sim is None:
             return self.path_default([])
         def info(name):
-            self.add_info("<b>Alias</b>: {}\n".format(hlesc(name)))
-            self.add_info("Value: {}\n\n".format(self.sim.names[name]))
+            self.add_info("<b>Alias</b>:    {}\n".format(hlesc(name)))
+            if self.tran:
+                self.add_info("<b>Alias</b>(t): {}\n".\
+                    format(hlesc(self._t(name, "obj"))))
+            self.add_info("Value:    {}\n".format(self.sim.names[name]))
+            if self.tran:
+                self.add_info("Value(t): {}\n".\
+                    format(hlesc(self._t(self.sim.names[name], "name"))))
             # search for objects
-            self.add_info("<b>Applied for</b>:\n")
+            self.add_info("\n<b>Applied for</b>:\n")
             for obj in self.sim.objects:
                 if obj.name == name:
                     self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
-        return self.path_std_items(path, 1, "Names", "name", self.sim.names, 
-            self.sim.namesord, 0, info)
+        return self.path_std_items(path, 1, "Names", "name", "obj", 
+            self.sim.names, self.sim.namesord, 0, info)
                             
     def path_invntr(self, path):
         if self.sim is None:
             return self.path_default([])
         def info(name):
-            self.add_info("<b>Invntr</b>: {}\n".format(hlesc(name)))
-            self.add_info("{}\n\n".format(self.sim.invntr[name]))
+            self.add_info("<b>Invntr</b>:    {}\n".format(hlesc(name)))
+            if self.tran:
+                self.add_info("<b>Invntr</b>(t): {}\n".\
+                    format(hlesc(self._t(name, "obj"))))
+            self.add_info("{}\n\n".format(hlesc(self.sim.invntr[name])))
+            if self.tran:
+                self.add_info("<i>Translated</i>\n{}\n\n".\
+                    format(hlesc(self._t(self.sim.invntr[name], "inv"))))
             # search for objects
             self.add_info("<b>Applied for</b>:\n")
             for obj in self.sim.objects:
                 if obj.name == name:
                     self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
-        return self.path_std_items(path, 1, "Invntr", "invntr", self.sim.invntr, 
-            self.sim.invntrord, 0, info)
+        return self.path_std_items(path, 1, "Invntr", "invntr", "obj", 
+            self.sim.invntr, self.sim.invntrord, 0, info)
 
 
     def path_casts(self, path):
@@ -1306,8 +1368,8 @@ class App(tkinter.Frame):
             for idx, obj in enumerate(self.sim.objects):
                 if obj.name == name:
                     self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
-        return self.path_std_items(path, 1, "Cast", "cast", self.sim.casts, 
-            self.sim.castsord, 0, info)
+        return self.path_std_items(path, 1, "Cast", "cast", "obj", 
+            self.sim.casts, self.sim.castsord, 0, info)
 
     def path_msgs(self, path):
         if self.sim is None:
@@ -1348,7 +1410,9 @@ class App(tkinter.Frame):
             self.add_info("  arg2:   {} (0x{:X})\n".format(msg.arg2, msg.arg2))
             self.add_info("  arg3:   {} (0x{:X})\n".format(msg.arg3, msg.arg3))
             self.add_info("\n{}\n".format(hlesc(msg.name)))
-
+            if self.tran:
+                self.add_info("\n<i>Translated:</i>\n{}\n".\
+                    format(hlesc(self._t(msg.name, "msg"))))
             self.add_info("\n<b>Used by dialog groups</b>:\n")
             for grp in self.sim.dlgs:
                 for act in grp.acts:
@@ -1503,7 +1567,6 @@ class App(tkinter.Frame):
             usedby(self.sim.objects)
             self.add_info("\n<b>Used by scenes</b>:\n")
             usedby(self.sim.scenes)
-            
         
     def path_test(self, path):
         self.update_gui("Test {}".format(path[1]))
@@ -1520,34 +1583,6 @@ class App(tkinter.Frame):
             self.add_info("Information panel for {}\n".format(path))
             for i in range(100):
                 self.add_info("  Item {}\n".format(i))
-        elif path[1] == "translate":
-            self.switch_view(0)
-            self.clear_info()
-            self.add_info("Save translation template\n    ")
-            def savepot():
-                f = open("save_{}.pot".format(self.sim.curr_part), "wb")
-                try:
-                    def saveitem(msgid, msgstr, msgctx):
-                        if msgctx:
-                            f.write("msgctx \"{}\"\n".format(hlesc(msgctx)).\
-                                encode("UTF-8"))
-                        f.write("msgid \"{}\"\n".format(hlesc(msgid)).\
-                            encode("UTF-8"))
-                        f.write("msgstr \"{}\"\n\n".format(hlesc(msgstr)).\
-                            encode("UTF-8"))
-                    # objects
-                    for obj in self.sim.objects:
-                        saveitem(obj.name, obj.name, "obj_{}".format(obj.idx))
-                    for scn in self.sim.scenes:
-                        saveitem(scn.name, scn.name, "scene_{}".format(scn.idx))
-                    
-                finally:
-                    f.close()
-            
-            btn = ttk.Button(self.text_view, text = "Save .POT", \
-                command = savepot)
-            self.text_view.window_create(tkinter.INSERT, window = btn)
-            
 
     def path_about(self, path):
         self.switch_view(0)
@@ -1572,10 +1607,20 @@ class App(tkinter.Frame):
             hlesc(self.app_path)))
         self.add_info("<b>Game folder</b>: {}\n".format(
             hlesc(self.last_fn)))
+        self.add_info("<b>Translation</b>: ")
+        if not polib:
+            self.add_info("<i><u>polib</u> not found</i>\n")
+        else:
+            if not self.tran:
+                self.add_info("<i>no tranlation file</i>\n")
+            else:
+                self.add_info(hlesc(self.tran_fn) + "\n")
+                
+        self.add_info("<b>Engine</b>:      ")
         if self.sim is None:
-            self.add_info("<i>Engine not initialized</i>\n")
+            self.add_info("<i>not initialized</i>\n")
         else:
-            self.add_info("<i>Engine works</i>\n\n")
+            self.add_info("<i>works</i>\n\n")
             self.add_info("  <b>Path</b>:    {}\n".format(
                 hlesc(self.sim.curr_path)))
             self.add_info("  <b>Speech</b>:  {}\n".format(
@@ -1689,10 +1734,10 @@ class App(tkinter.Frame):
             self.open_path("")
             self.clear_hist()
         
-        
     def open_data_from(self, folder):
         self.last_fn = folder
         try:
+            self.tran = None
             self.sim = petka.Engine()
             self.sim.load_data(folder, "cp1251")
             self.sim.open_part(0, 0)
@@ -1707,13 +1752,92 @@ class App(tkinter.Frame):
                 format(hlesc(folder), hlesc(traceback.format_exc())))
             self.clear_hist()
 
+    def on_tran_save(self):
+        # save dialog
+        fn = filedialog.asksaveasfilename(parent = self,
+            title = "Save translate template (.pot)",
+            filetypes = [('PO Template', ".pot"), ('all files', '.*')],
+            initialdir = os.path.abspath(os.curdir))
+        if not fn: return # save canceled
+        # save template
+        try:
+            po = polib.POFile()
+            po.metadata = {
+                'MIME-Version': '1.0',
+                'Content-Type': 'text/plain; charset=utf-8',
+                'Content-Transfer-Encoding': '8bit',
+            }
+            used = []
+            def saveitem(text, cmt = None):
+                if text in used: return
+                used.append(text)
+                entry = polib.POEntry(
+                    msgid = text, msgstr = text, comment = cmt)
+                po.append(entry)                
+            for rec in self.sim.objects:
+                saveitem(rec.name, "obj_{}".format(rec.idx))
+            for rec in self.sim.scenes:
+                saveitem(rec.name, "scn_{}".format(rec.idx))
+            for idx, name in enumerate(self.sim.namesord):
+                saveitem(self.sim.names[name], 
+                    "name_{}, {}".format(idx, name))
+            for idx, name in enumerate(self.sim.invntrord):
+                saveitem(self.sim.invntr[name],
+                    "inv_{}, {}".format(idx, name))
+            for idx, msg in enumerate(self.sim.msgs):
+                saveitem(msg.name, 
+                    "msg_{}, {} - {}".format(idx, msg.obj.idx, msg.obj.name))
+            po.save(fn)
+        except:
+            self.switch_view(0)
+            self.clear_info()
+            self.add_info("Error saving \"{}\" \n\n{}".\
+                format(hlesc(fn), hlesc(traceback.format_exc())))
+
+    def on_tran_load(self):
+        ft = [\
+            ('PO files', '.po'),
+            ('all files', '.*')]
+        fn = filedialog.askopenfilename(parent = self, 
+            title = "Open translation for current part",
+            filetypes = ft,
+            initialdir = os.path.abspath(os.curdir))
+        if not fn: return
+        os.chdir(os.path.dirname(fn))
+        self.open_tran_from(fn)
+            
+    def open_tran_from(self, fn):
+        self.tran_fn = fn
+        try:
+            po = polib.pofile(fn)
+            self.tran = {"obj": {}, "scn": {}, "name": {}, "inv": {},
+              "msg": {}, "_": {}}
+            for tr in po.translated_entries():
+                if tr.comment:
+                    pref = tr.comment.split("_", 1)
+                    if pref[0] in self.tran:
+                        self.tran[pref[0]][tr.msgid] = tr.msgstr
+                    self.tran["_"][tr.msgid] = tr.msgstr
+        except:
+            self.switch_view(0)
+            self.clear_info()
+            self.add_info("Error opening \"{}\" \n\n{}".\
+                format(hlesc(fn), hlesc(traceback.format_exc())))
+
 def main():
     root = tkinter.Tk()
     app = App(master = root)
-    if len(sys.argv) > 1:
-        app.start_act.append(["load", sys.argv[1]])
-    for arg in sys.argv[2:]:
-        app.start_act.append(["open", arg])
+    argv = sys.argv[1:]
+    while len(argv) > 0:
+        if argv[0] == "-d": # open data
+            app.start_act.append(["load", argv[1]])
+            argv = argv[2:]
+        elif argv[0] == "-t": # open translation
+            app.start_act.append(["tran", argv[1]])
+            argv = argv[2:]
+        else:
+            app.start_act.append(["open", argv[0]])
+            argv = argv[1:]
     app.mainloop()
 
     


Commit: 81d2dbbea5d381bb762bfabd064d270858f1732f
    https://github.com/scummvm/scummvm-tools/commit/81d2dbbea5d381bb762bfabd064d270858f1732f
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: add help file for translations

Changed paths:
  A engines/petka/help/translate.txt


diff --git a/engines/petka/help/translate.txt b/engines/petka/help/translate.txt
new file mode 100644
index 000000000..810fc40fc
--- /dev/null
+++ b/engines/petka/help/translate.txt
@@ -0,0 +1,7 @@
+Для переводчиков
+
+Для переводчиков программа может:
+
+ * Создать шаблон для перевода в формате POT (Translation->Save template)
+ * Загрузить перевод из файла PO (Translation->Save translation)
+ * Отображение переводов названий и текстов у всех объектов


Commit: ece09a8a911ac3c4fa5c6319b6ed5bca83e52b24
    https://github.com/scummvm/scummvm-tools/commit/ece09a8a911ac3c4fa5c6319b6ed5bca83e52b24
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Add license file

Changed paths:
  A engines/petka/help/license.txt
    engines/petka/help/index.txt
    engines/petka/help/list
    engines/petka/help/translate.txt


diff --git a/engines/petka/help/index.txt b/engines/petka/help/index.txt
index 28af58a6e..8ae290bd5 100644
--- a/engines/petka/help/index.txt
+++ b/engines/petka/help/index.txt
@@ -4,6 +4,7 @@
 
  * <a href="/help/changes">Что нового</a>
  * <a href="/help/faq">Часто задаваемые вопросы</a>
+ * <a href="/help/license">Лицензии</a>
 
 Отображаемые разделы
 
diff --git a/engines/petka/help/license.txt b/engines/petka/help/license.txt
new file mode 100644
index 000000000..d836c5edd
--- /dev/null
+++ b/engines/petka/help/license.txt
@@ -0,0 +1,61 @@
+Лицении
+
+Данная программа поставляется под лицензией MIT.
+
+Кратко:
+
+ * Можно пользоваться, дорабатывать и распространять свободно
+ * При распространении надо копировать этот файл
+ * Никаких гарантий
+
+Полный текст приведён ниже.
+
+Используемые библиотеки и ПО:
+
+ * Python (https://www.python.org/) - PSF LICENSE AGREEMENT
+ * Pillow (https://pypi.python.org/pypi/Pillow/) - Apache License
+ * polib (https://pypi.python.org/pypi/polib) - MIT
+ * cx_Freeze (http://cx-freeze.sourceforge.net/) - PSF LICENSE AGREEMENT
+ 
+Copyright (c) 2014 romiq.kh at gmail.com
+
+Данная лицензия разрешает, безвозмездно, лицам, получившим копию данного 
+программного обеспечения и сопутствующей документации (в дальнейшем именуемыми 
+"Программное Обеспечение"), использовать Программное Обеспечение без 
+ограничений, включая неограниченное право на использование, копирование, 
+изменение, объединение, публикацию, распространение, сублицензирование и/или 
+продажу копий Программного Обеспечения, также как и лицам, которым 
+предоставляется данное Программное Обеспечение, при соблюдении следующих 
+условий:
+
+Вышеупомянутый копирайт и данные условия должны быть включены во все копии 
+или значимые части данного Программного Обеспечения.
+
+ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ ЛЮБОГО ВИДА 
+ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ 
+ГАРАНТИЯМИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И 
+НЕНАРУШЕНИЯ ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ 
+ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ ИЛИ ДРУГИХ ТРЕБОВАНИЙ ПО 
+ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ ИНОМУ, ВОЗНИКШИМ ИЗ, ИМЕЮЩИМ ПРИЧИНОЙ ИЛИ 
+СВЯЗАННЫМ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ПРОГРАММНОГО 
+ОБЕСПЕЧЕНИЯ ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ. 
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy 
+of this software and associated documentation files (the "Software"), to deal 
+in the Software without restriction, including without limitation the rights to 
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
+of the Software, and to permit persons to whom the Software is furnished to do 
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in 
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
+SOFTWARE. 
+
diff --git a/engines/petka/help/list b/engines/petka/help/list
index 984865268..dbab18eeb 100644
--- a/engines/petka/help/list
+++ b/engines/petka/help/list
@@ -1,6 +1,7 @@
 index
 changes
 faq
+license
 parts
 res
 objs
diff --git a/engines/petka/help/translate.txt b/engines/petka/help/translate.txt
index 810fc40fc..49b98535c 100644
--- a/engines/petka/help/translate.txt
+++ b/engines/petka/help/translate.txt
@@ -4,4 +4,11 @@
 
  * Создать шаблон для перевода в формате POT (Translation->Save template)
  * Загрузить перевод из файла PO (Translation->Save translation)
- * Отображение переводов названий и текстов у всех объектов
+ * Покаывать перевод названий и текстов у всех объектов
+ 
+ 
+Для работы этих функций необходима библиотека polib.
+ 
+ * https://pypi.python.org/pypi/polib
+
+Если библиотека не установлена меню Translate отображаться не будет. 


Commit: 2ecdc95048eb9a4f0831ed656bd57c8b15f31643
    https://github.com/scummvm/scummvm-tools/commit/2ecdc95048eb9a4f0831ed656bd57c8b15f31643
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: refactor

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index ddb8103e9..4d1f0c079 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -52,6 +52,15 @@ def fmt_hl(loc, desc):
 def fmt_cmt(cmt):
     return "<font color=\"#4d4d4d\">{}</font>".format(cmt)
 
+def fmt_arg(value):
+    if value < 10:
+        return "{}".format(value)
+    elif value == 0xffff:
+        return "-1"
+    else:
+        return "0x{:X}".format(value)
+    
+
 # thanx to http://effbot.org/zone/tkinter-text-hyperlink.htm
 class HyperlinkManager:
     def __init__(self, text):
@@ -964,15 +973,14 @@ class App(tkinter.Frame):
             def usedby(lst):
                 for idx, rec in enumerate(lst):
                     ru = False
-                    for act_id, act_cond, act_arg, ops in rec.acts:
+                    for act in rec.acts:
                         if ru: break
-                        for op_id, op_code, op_res, op4, op5 in ops:
-                            if res_id == op_res:
+                        for op in ops:
+                            if res_id == op.op_arg1:
                                 self.add_info("  " + 
                                     self.fmt_hl_obj_scene(rec.idx, True) + "\n")
                                 ru = True
                                 break
-                            #print(op_id, op_code, op_res, op4, op5)
 
             self.add_info("\n\n<b>Used by objects</b>:\n")
             usedby(self.sim.objects)
@@ -1184,47 +1192,45 @@ class App(tkinter.Frame):
             resused = []
             dlgused = []
             self.add_info("\n<b>Handlers</b>: {}\n".format(len(rec.acts)))
-            for idx, (act_op, act_status, act_ref, ops) in enumerate(rec.acts):
-                msg = fmt_opcode(act_op)
+            for idx, act in enumerate(rec.acts):
+                msg = fmt_opcode(act.act_op)
                 cmt = ""
-                if act_status != 0xff or act_ref != 0xffff:
-                    if act_ref == rec.idx:
+                if act.act_status != 0xff or act.act_ref != 0xffff:
+                    act_ref = act.act_ref
+                    if act.act_ref == rec.idx:
                         act_ref = "THIS"
                     else:
-                        if act_ref in self.sim.obj_idx:
-                            cmt = fmt_cmt(" // " + self.fmt_hl_obj(act_ref, 
+                        if act.act_ref in self.sim.obj_idx:
+                            cmt = fmt_cmt(" // " + self.fmt_hl_obj(act.act_ref, 
                                 True))
-                            act_ref = self.fmt_hl_obj(act_ref)
+                            act_ref = self.fmt_hl_obj(act.act_ref)
                         else:
-                            act_ref = "0x{:X}".format(act_ref)
-                    msg += " 0x{:02X} {}".format(act_status, act_ref)
+                            act_ref = "0x{:X}".format(act.act_ref)
+                    msg += " 0x{:02X} {}".format(act.act_status, act_ref)
                 self.add_info("  {}) <u>on {}</u>, ops: {}{}\n".format(\
-                    idx, msg, len(ops), cmt))
-                for oidx, op in enumerate(ops):
-                    self.add_info("    {}) {} ".format(oidx, fmt_opcode(op[1])))
+                    idx, msg, len(act.ops), cmt))
+                for oidx, op in enumerate(act.ops):
+                    self.add_info("    {}) {} ".format(oidx, 
+                        fmt_opcode(op.op_code)))
                     cmt = ""
-                    if op[0] == rec.idx:
+                    if op.op_ref == rec.idx:
                         self.add_info("THIS")
                     else:
-                        self.add_info(self.fmt_hl_obj_scene(op[0]))
-                        cmt = fmt_cmt(" // " + self.fmt_hl_obj_scene(op[0], 
+                        self.add_info(self.fmt_hl_obj_scene(op.op_ref))
+                        cmt = fmt_cmt(" // " + self.fmt_hl_obj_scene(op.op_ref, 
                             True))
                     msg = ""
-                    if op[2] != 0xffff:
-                        if op[2] not in resused and op[2] in self.sim.res:
-                            resused.append(op[2])
-                    for arg in op[2:]:
-                        msg += " "
-                        if arg < 10:
-                            msg += "{}".format(arg)
-                        elif arg == 0xffff:
-                            msg += "-1"
-                        else:
-                            msg += "0x{:X}".format(arg)
+                    if op.op_arg1 != 0xffff:
+                        if op.op_arg1 not in resused and \
+                                op.op_arg1 in self.sim.res:
+                            resused.append(op.op_arg1)
+                    msg += " " + fmt_arg(op.op_arg1)
+                    msg += " " + fmt_arg(op.op_arg2)
+                    msg += " " + fmt_arg(op.op_arg3)
                     self.add_info("{}{}\n".format(msg, cmt))
-                    if op[1] == 0x11: # DIALOG
-                        if op[0] not in dlgused:
-                            dlgused.append(op[0])
+                    if op.op_code == 0x11: # DIALOG
+                        if op.op_ref not in dlgused:
+                            dlgused.append(op.op_ref)
                     
             if len(resused) > 0:
                 self.add_info("\n<b>Used resources</b>: {}\n".\
@@ -1254,13 +1260,13 @@ class App(tkinter.Frame):
             wasmsg = False
             for obj2 in self.sim.objects + self.sim.scenes:
                 if obj2.idx == rec.idx: continue
-                for idx, (act_op, act_status, act_ref, ops) in \
+                for idx, act in \
                         enumerate(obj2.acts):
-                    for oidx, op in enumerate(ops):
-                        if op[0] == rec.idx:
-                            arr = oplst.get(op[1], [])
-                            arr.append((obj2.idx, act_op, idx))
-                            oplst[op[1]] = arr
+                    for oidx, op in enumerate(act.ops):
+                        if op.op_ref == rec.idx:
+                            arr = oplst.get(op.op_code, [])
+                            arr.append((obj2.idx, act.act_op, idx))
+                            oplst[op.op_code] = arr
                             break
 
             klst = list(petka.OPCODES.keys())
@@ -1553,10 +1559,11 @@ class App(tkinter.Frame):
             def usedby(lst):
                 for idx, rec in enumerate(lst):
                     ru = False
-                    for act_id, act_cond, act_arg, ops in rec.acts:
+                    for act in rec.acts:
                         if ru: break
-                        for op_id, op_code, op_res, op4, op5 in ops:
-                            if op_code == 0x11 and op_id == grp.idx: # DIALOG 
+                        for op in act.ops:
+                            if op.op_code == 0x11 and \
+                                    op.op_ref == grp.idx: # DIALOG 
                                 self.add_info("  " + 
                                     self.fmt_hl_obj_scene(rec.idx, True) + "\n")
                                 ru = True
@@ -1750,7 +1757,7 @@ class App(tkinter.Frame):
             self.clear_info()
             self.add_info("Error opening \"{}\" \n\n{}".\
                 format(hlesc(folder), hlesc(traceback.format_exc())))
-            self.clear_hist()
+            #self.clear_hist()
 
     def on_tran_save(self):
         # save dialog
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 3dd31ef1f..638d5b978 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -84,14 +84,29 @@ class ScrObject:
     def __init__(self, idx, name):
         self.idx = idx
         self.name = name
-        self.acts = None
-        self.cast = None
+        self.acts = None # action hadlers
+        self.cast = None # object color (CASTS.INI)
+
+class ScrActObject:
+    def __init__(self, act_op, act_status, act_ref):
+        self.act_op = act_op         # handler: opcode filter
+        self.act_status = act_status # handler: status filter
+        self.act_ref = act_ref       # handler: object idx filter
+        self.ops = None              # operations
+
+class ScrOpObject:
+    def __init__(self, op_ref, op_code, op_arg1, op_arg2, op_arg3):
+        self.op_ref = op_ref # object idx
+        self.op_code = op_code # opcode
+        self.op_arg1 = op_arg1 # resource id, etc
+        self.op_arg2 = op_arg2
+        self.op_arg3 = op_arg3
 
 class MsgObject:
     def __init__(self, idx, wav, arg1, arg2, arg3):
         self.idx = idx
-        self.wav = wav
-        self.arg1 = arg1
+        self.wav = wav   # wav filename
+        self.arg1 = arg1 # reference to object
         self.arg2 = arg2
         self.arg3 = arg3
         self.name = None
@@ -99,34 +114,34 @@ class MsgObject:
 class DlgGrpObject:
     def __init__(self, idx, num_acts, arg1):
         self.idx = idx
-        self.num_acts = num_acts
+        self.num_acts = num_acts # store array length while loading
         self.arg1 = arg1
-        self.acts = None
+        self.acts = None # dialog handlers
 
 class DlgActObject:
     def __init__(self, num_dlgs, opcode, ref, arg1, arg2):
-        self.num_dlgs = num_dlgs
-        self.opcode = opcode
-        self.ref = ref
+        self.num_dlgs = num_dlgs # store array length while loading
+        self.opcode = opcode # handler: opcode filter
+        self.ref = ref       # handler: object idx filter
         self.arg1 = arg1
         self.arg2 = arg2
-        self.dlgs = None
-        self.obj = None
+        self.dlgs = None     # dialogs
+        self.obj = None      # handler object
 
 class DlgObject:
     def __init__(self, op_start, arg1, arg2):
-        self.op_start = op_start
+        self.op_start = op_start # start position
         self.arg1 = arg1
         self.arg2 = arg2
-        self.ops = None
+        self.ops = None          # operations list
 
 class DlgOpObject:
     def __init__(self, opcode, arg, ref, pos):
-        self.opcode = opcode
-        self.arg = arg
-        self.ref = ref
-        self.msg = None
-        self.pos = pos
+        self.opcode = opcode    # dialog opcode
+        self.arg = arg          # argument (ref, offset etc.)
+        self.ref = ref          # message idx
+        self.msg = None         # message
+        self.pos = pos          # position in opcodes list
         
 class Engine:
     def __init__(self):
@@ -287,15 +302,17 @@ class Engine:
             off += 4
             acts = []
             for i in range(num_act):
-                act_id, act_cond, act_arg, num_op = struct.unpack_from(\
+                act_op, act_status, act_ref, num_op = struct.unpack_from(\
                     "<HBHI", data[off:off + 9])
                 off += 9
-                ops = []
+                act = ScrActObject(act_op, act_status, act_ref)
+                act.ops = []
                 for j in range(num_op):
                     op = struct.unpack_from("<5H", data[off:off + 10])
                     off += 10
-                    ops.append(op)
-                acts.append([act_id, act_cond, act_arg, ops])
+                    op = ScrOpObject(*op)
+                    act.ops.append(op)
+                acts.append(act)
             rec = ScrObject(obj_id, name)
             rec.acts = acts
             return off, rec


Commit: adc0fdd3ae7ef458aa32c1ac702ca1bf8a9f4af1
    https://github.com/scummvm/scummvm-tools/commit/adc0fdd3ae7ef458aa32c1ac702ca1bf8a9f4af1
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Fix loading BGS.INI, add support info about start scene

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 4d1f0c079..e8eea1732 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -803,6 +803,12 @@ class App(tkinter.Frame):
             format(len(self.sim.msgs)))
         self.add_info("  Dialog groups: <a href=\"/dlgs\">{}</a>\n".\
             format(len(self.sim.dlgs)))
+        scn = hlesc(self.sim.start_scene)
+        for scene in self.sim.scenes:
+            if scene.name == self.sim.start_scene:
+                scn = self.fmt_hl_scene(scene.idx, True)
+                break
+        self.add_info("  Start scene:   {}\n".format(scn))
     
 
     def path_default(self, path):
@@ -1628,11 +1634,13 @@ class App(tkinter.Frame):
             self.add_info("<i>not initialized</i>\n")
         else:
             self.add_info("<i>works</i>\n\n")
-            self.add_info("  <b>Path</b>:    {}\n".format(
+            self.add_info("  <b>Path</b>:       {}\n".format(
                 hlesc(self.sim.curr_path)))
-            self.add_info("  <b>Speech</b>:  {}\n".format(
+            self.add_info("  <b>Start</b>:      {}.{}\n".format(
+                self.sim.start_part, self.sim.start_chap))
+            self.add_info("  <b>Speech</b>:     {}\n".format(
                 hlesc(self.sim.curr_speech)))
-            self.add_info("  <b>Disk ID</b>: {}\n\n".format(
+            self.add_info("  <b>Disk ID</b>:    {}\n\n".format(
                 hlesc(self.sim.curr_diskid)))
             self.path_info_outline()
             
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 638d5b978..7851b13de 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -257,9 +257,9 @@ class Engine:
         # load BGS.INI
         self.bgs_ini = {}
         self.start_scene = None
-        pf = self.fman.find_path(self.curr_path + "bgs.ini")
-        if pf:
-            f = open(pf, "rb")
+        bgsfn = self.curr_path + "bgs.ini"
+        if self.fman.exists(bgsfn):
+            f = self.fman.read_file_stream(bgsfn)
             try:
                 self.bgs_ini = self.parse_ini(f)
             finally:


Commit: 641d9860b56537cf808cff0f865e25e02a7028b2
    https://github.com/scummvm/scummvm-tools/commit/641d9860b56537cf808cff0f865e25e02a7028b2
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Fix loading BGS.INI, add support info about start scene

Changed paths:
    engines/petka/petka/engine.py


diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 7851b13de..e390a9fa1 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -276,6 +276,11 @@ class Engine:
                 self.fman.load_store(ini[strf], 1)
         # load script.dat, backgrnd.bg and resources.qrc
         self.load_script()
+        # parse enter areas
+        for scene in self.scenes:
+            if scene.name in self.bgs_ini:
+                print(self.bgs_ini[scene.name])
+
         # load names & invntr
         self.load_names()
         # load dialogs


Commit: c3fc6f033132a1433f76eb0624a7ebcca25504b0
    https://github.com/scummvm/scummvm-tools/commit/c3fc6f033132a1433f76eb0624a7ebcca25504b0
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Add enter areas information

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index e8eea1732..d8b1d4f79 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1286,6 +1286,19 @@ class App(tkinter.Frame):
                       " #{}".format(hid) + fmt_cmt(" // " + 
                       self.fmt_hl_obj_scene(oid, True)) + "\n")
 
+            # enter areas
+            if not isobj and rec.entareas:
+                self.add_info("\n<b>Enter areas</b>: {}\n".format(
+                    len(rec.entareas)))
+                for sf, oo in rec.entareas:
+                    self.add_info("  <i>from</i>: {}\n".format(
+                        self.fmt_hl_scene(sf.idx, True)))
+                    self.add_info("    <i>on</i>: {}\n".format(
+                        self.fmt_hl_obj(oo.idx, True)))
+                
+                
+                
+
     def path_std_items(self, path, level, guiname, guiitem, tt, lst, lst_idx, 
             lbmode, cb):
         self.switch_view(0)
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index e390a9fa1..4fe0a9af2 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -278,9 +278,25 @@ class Engine:
         self.load_script()
         # parse enter areas
         for scene in self.scenes:
+            scene.entareas = None
             if scene.name in self.bgs_ini:
-                print(self.bgs_ini[scene.name])
-
+                scene.entareas = []
+                for key in self.bgs_ini["__order__"][scene.name]:
+                    # search scene
+                    sf = None
+                    for scenefrom in self.scenes:
+                        if scenefrom.name == key:
+                            sf = scenefrom
+                            break
+                    value = self.bgs_ini[scene.name][key]
+                    # search objects
+                    oo = None
+                    for objon in self.objects:
+                        if objon.name == value:
+                            oo = objon
+                            break
+                    if sf and oo:
+                        scene.entareas.append((sf, oo))
         # load names & invntr
         self.load_names()
         # load dialogs


Commit: c31c1f8f7d349dd5b77f4ff7ab5fb0e2f369f508
    https://github.com/scummvm/scummvm-tools/commit/c31c1f8f7d349dd5b77f4ff7ab5fb0e2f369f508
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: transliterate templates

Changed paths:
    engines/petka/help/license.txt
    engines/petka/help/translate.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/license.txt b/engines/petka/help/license.txt
index d836c5edd..31af67f0b 100644
--- a/engines/petka/help/license.txt
+++ b/engines/petka/help/license.txt
@@ -15,8 +15,9 @@
  * Python (https://www.python.org/) - PSF LICENSE AGREEMENT
  * Pillow (https://pypi.python.org/pypi/Pillow/) - Apache License
  * polib (https://pypi.python.org/pypi/polib) - MIT
+ * transliterate (https://pypi.python.org/pypi/transliterate) - GPL 2.0/LGPL 2.1
  * cx_Freeze (http://cx-freeze.sourceforge.net/) - PSF LICENSE AGREEMENT
- 
+
 Copyright (c) 2014 romiq.kh at gmail.com
 
 Данная лицензия разрешает, безвозмездно, лицам, получившим копию данного 
diff --git a/engines/petka/help/translate.txt b/engines/petka/help/translate.txt
index 49b98535c..bbd2dfed7 100644
--- a/engines/petka/help/translate.txt
+++ b/engines/petka/help/translate.txt
@@ -3,12 +3,21 @@
 Для переводчиков программа может:
 
  * Создать шаблон для перевода в формате POT (Translation->Save template)
+ * Создать шаблон в транслите формате POT 
+   (Translation->Save transliterate template)
  * Загрузить перевод из файла PO (Translation->Save translation)
  * Покаывать перевод названий и текстов у всех объектов
  
- 
 Для работы этих функций необходима библиотека polib.
  
  * https://pypi.python.org/pypi/polib
 
-Если библиотека не установлена меню Translate отображаться не будет. 
+Если библиотека не установлена меню "Translate" отображаться не будет. 
+
+Для перевода в транслит необходима библиотека transliterate.
+ 
+ * https://pypi.python.org/pypi/transliterate
+
+Если библиотека не установлена пункт меню "Save transliterate template" 
+  отображаться не будет. 
+
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index d8b1d4f79..ed790030c 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -25,6 +25,11 @@ try:
 except ImportError:
     polib = None
 
+try:
+    import transliterate
+except ImportError:
+    transliterate = None
+
 import petka
 
 APPNAME = "P1&2 Explorer"
@@ -332,6 +337,10 @@ class App(tkinter.Frame):
             menutran.add_command(
                     command = self.on_tran_save,
                     label = "Save template (.pot)")
+            if transliterate:
+                menutran.add_command(
+                        command = self.on_tran_save_tlt,
+                        label = "Save transliterate template (.pot)")
             menutran.add_command(
                     command = self.on_tran_load,
                     label = "Load translation (.po)")
@@ -1374,8 +1383,11 @@ class App(tkinter.Frame):
         if self.sim is None:
             return self.path_default([])
         def info(name):
-            self.add_info("<b>Cast</b>: {}\n".format(hlesc(name)))
-            self.add_info("Value: {}\n".format(self.sim.casts[name]))
+            self.add_info("<b>Cast</b>:    {}\n".format(hlesc(name)))
+            if self.tran:
+                self.add_info("<b>Cast</b>(t): {}\n".\
+                    format(hlesc(self._t(name, "obj"))))
+            self.add_info("Value:    {}\n".format(self.sim.casts[name]))
             try:
                 val = self.sim.casts[name].split(" ")
                 val = [x for x in val if x]
@@ -1781,9 +1793,16 @@ class App(tkinter.Frame):
             #self.clear_hist()
 
     def on_tran_save(self):
+        self.on_tran_save_real()
+
+    def on_tran_save_tlt(self):
+        self.on_tran_save_real(True)
+        
+    def on_tran_save_real(self, tlt = False):
         # save dialog
         fn = filedialog.asksaveasfilename(parent = self,
-            title = "Save translate template (.pot)",
+            title = "Save translate template (.pot)" + 
+                " (transliterate)" if tlt else "",
             filetypes = [('PO Template', ".pot"), ('all files', '.*')],
             initialdir = os.path.abspath(os.curdir))
         if not fn: return # save canceled
@@ -1799,8 +1818,12 @@ class App(tkinter.Frame):
             def saveitem(text, cmt = None):
                 if text in used: return
                 used.append(text)
+                ts = text
+                if tlt:
+                    ts = transliterate.utils.translit(text, "ru", \
+                        reversed = True)
                 entry = polib.POEntry(
-                    msgid = text, msgstr = text, comment = cmt)
+                    msgid = text, msgstr = ts, comment = cmt)
                 po.append(entry)                
             for rec in self.sim.objects:
                 saveitem(rec.name, "obj_{}".format(rec.idx))


Commit: 396efadba749d6d3e13582692a4d74bcdeecc77b
    https://github.com/scummvm/scummvm-tools/commit/396efadba749d6d3e13582692a4d74bcdeecc77b
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: release 0.2n; remove tranliterate library

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/help/license.txt
    engines/petka/help/translate.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index b67406c46..92a9d4c2e 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,12 +1,23 @@
 Что нового
 ==========
 
+2014-05-21 версия 0.2n
+----------------------
+Функуция транслитерации заменена на собсвенную.
+
+2014-05-21 версия 0.2m
+----------------------
+Добавлено отображение точек входа на сцену из других сцен (BGS.INI)
+Исправлена загрузка файла BGS.INI из STR файла
+Добавлена функция сохранения шаблона перевода в транслите (Теперь можно сразу 
+оценить где перевод может быть выполнен).
+
 2014-05-20 версия 0.2l
 ----------------------
 Добавлена функция создания шаблона для перевода (.pot)
 Добавлена загрузка перевода (.po)
 Изменены все разделы где может отображаться переведённа информация
-Изменен порядок загрузкт данных из командной строки
+Изменен порядок загрузки данных из командной строки
 
 2014-05-16 версия 0.2k
 ----------------------
diff --git a/engines/petka/help/license.txt b/engines/petka/help/license.txt
index 31af67f0b..fbdb990e3 100644
--- a/engines/petka/help/license.txt
+++ b/engines/petka/help/license.txt
@@ -15,7 +15,6 @@
  * Python (https://www.python.org/) - PSF LICENSE AGREEMENT
  * Pillow (https://pypi.python.org/pypi/Pillow/) - Apache License
  * polib (https://pypi.python.org/pypi/polib) - MIT
- * transliterate (https://pypi.python.org/pypi/transliterate) - GPL 2.0/LGPL 2.1
  * cx_Freeze (http://cx-freeze.sourceforge.net/) - PSF LICENSE AGREEMENT
 
 Copyright (c) 2014 romiq.kh at gmail.com
diff --git a/engines/petka/help/translate.txt b/engines/petka/help/translate.txt
index bbd2dfed7..c059d68b3 100644
--- a/engines/petka/help/translate.txt
+++ b/engines/petka/help/translate.txt
@@ -14,10 +14,3 @@
 
 Если библиотека не установлена меню "Translate" отображаться не будет. 
 
-Для перевода в транслит необходима библиотека transliterate.
- 
- * https://pypi.python.org/pypi/transliterate
-
-Если библиотека не установлена пункт меню "Save transliterate template" 
-  отображаться не будет. 
-
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index ed790030c..2821de8ba 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -25,15 +25,10 @@ try:
 except ImportError:
     polib = None
 
-try:
-    import transliterate
-except ImportError:
-    transliterate = None
-
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2l 2014-05-20"
+VERSION = "v0.2m 2014-05-21"
 
 def hlesc(value):
     if value is None:
@@ -43,19 +38,9 @@ def hlesc(value):
 def cesc(value):
     return value.replace("\\", "\\\\").replace("\"", "\\\"")
 
-def fmt_opcode(opcode):
-    return petka.OPCODES.get(opcode, ["<font color=\"red\">OP{:04X}</font>".\
-        format(opcode)])[0]
-
-def fmt_dlgop(opcode):
-    return petka.DLGOPS.get(opcode, ["<font color=\"red\">OP{:02X}</font>".\
-        format(opcode)])[0]
-
 def fmt_hl(loc, desc):
     return "<a href=\"{}\">{}</a>".format(loc, desc)
 
-def fmt_cmt(cmt):
-    return "<font color=\"#4d4d4d\">{}</font>".format(cmt)
 
 def fmt_arg(value):
     if value < 10:
@@ -65,7 +50,40 @@ def fmt_arg(value):
     else:
         return "0x{:X}".format(value)
     
-
+def translit(text):
+    ru = "абвгдеёзийклмнопрстуфхъыьэАБВГДЕЁЗИЙКЛМНОПРСТУФХЪЫЬЭ"
+    en = "abvgdeezijklmnoprstufh'y'eABVGDEEZIJKLMNOPRSTUFH'Y'Э"
+    sl = {
+        "ж": "zh",
+        "ц": "ts",
+        "ч": "ch",
+        "ш": "sh",
+        "щ": "sch",
+        "ÑŽ": "yu",
+        "я": "ya",
+        "Ж": "Zh",
+        "Ц": "Ts",
+        "Ч": "Ch",
+        "Ш": "Sh",
+        "Щ": "Sch",
+        "Ю": "Yu",
+        "Я": "Ya"
+    }
+    ret = ""
+    allcaps = True
+    for ch in text:
+        ps = ru.find(ch)
+        if ps > 0:
+            ret += en[ps]
+        elif ch in sl:
+            ret += sl[ch]
+        else:
+            ret += ch
+        allcaps = (allcaps and ch.upper() == ch)
+    if allcaps:
+        ret = ret.upper()
+    return ret
+    
 # thanx to http://effbot.org/zone/tkinter-text-hyperlink.htm
 class HyperlinkManager:
     def __init__(self, text):
@@ -337,10 +355,9 @@ class App(tkinter.Frame):
             menutran.add_command(
                     command = self.on_tran_save,
                     label = "Save template (.pot)")
-            if transliterate:
-                menutran.add_command(
-                        command = self.on_tran_save_tlt,
-                        label = "Save transliterate template (.pot)")
+            menutran.add_command(
+                    command = self.on_tran_save_tlt,
+                    label = "Save transliterate template (.pot)")
             menutran.add_command(
                     command = self.on_tran_load,
                     label = "Load translation (.po)")
@@ -739,6 +756,17 @@ class App(tkinter.Frame):
             return self.tran["_"][value]
         return value
 
+    def fmt_opcode(self, opcode):
+        return petka.OPCODES.get(opcode, ["<font color=\"red\">OP{:04X}</font>".\
+            format(opcode)])[0]
+
+    def fmt_dlgop(self, opcode):
+        return petka.DLGOPS.get(opcode, ["<font color=\"red\">OP{:02X}</font>".\
+            format(opcode)])[0]
+
+    def fmt_cmt(self, cmt):
+        return "<font color=\"#4d4d4d\">{}</font>".format(cmt)
+
     def fmt_hl_rec(self, lst_idx, pref, rec_id, full, tt):
         if rec_id in lst_idx:
             fmt = fmt_hl("/{}/{}".format(pref, rec_id), str(rec_id))
@@ -1201,14 +1229,14 @@ class App(tkinter.Frame):
                             msg += "-1"
                         else:
                             msg += "0x{:X}".format(arg)
-                    self.add_info(msg + fmt_cmt(" // " + self.fmt_hl_obj(
+                    self.add_info(msg + self.fmt_cmt(" // " + self.fmt_hl_obj(
                         ref[0].idx, True)) + "\n")
 
             resused = []
             dlgused = []
             self.add_info("\n<b>Handlers</b>: {}\n".format(len(rec.acts)))
             for idx, act in enumerate(rec.acts):
-                msg = fmt_opcode(act.act_op)
+                msg = self.fmt_opcode(act.act_op)
                 cmt = ""
                 if act.act_status != 0xff or act.act_ref != 0xffff:
                     act_ref = act.act_ref
@@ -1216,8 +1244,8 @@ class App(tkinter.Frame):
                         act_ref = "THIS"
                     else:
                         if act.act_ref in self.sim.obj_idx:
-                            cmt = fmt_cmt(" // " + self.fmt_hl_obj(act.act_ref, 
-                                True))
+                            cmt = self.fmt_cmt(" // " + self.fmt_hl_obj(
+                                act.act_ref, True))
                             act_ref = self.fmt_hl_obj(act.act_ref)
                         else:
                             act_ref = "0x{:X}".format(act.act_ref)
@@ -1226,14 +1254,14 @@ class App(tkinter.Frame):
                     idx, msg, len(act.ops), cmt))
                 for oidx, op in enumerate(act.ops):
                     self.add_info("    {}) {} ".format(oidx, 
-                        fmt_opcode(op.op_code)))
+                        self.fmt_opcode(op.op_code)))
                     cmt = ""
                     if op.op_ref == rec.idx:
                         self.add_info("THIS")
                     else:
                         self.add_info(self.fmt_hl_obj_scene(op.op_ref))
-                        cmt = fmt_cmt(" // " + self.fmt_hl_obj_scene(op.op_ref, 
-                            True))
+                        cmt = self.fmt_cmt(" // " + self.fmt_hl_obj_scene(
+                            op.op_ref, True))
                     msg = ""
                     if op.op_arg1 != 0xffff:
                         if op.op_arg1 not in resused and \
@@ -1288,11 +1316,11 @@ class App(tkinter.Frame):
             klst.sort()
             for k in klst:
                 if k not in oplst: continue
-                self.add_info("\n<b>Used in " + fmt_opcode(k) + "</b>:\n")
+                self.add_info("\n<b>Used in " + self.fmt_opcode(k) + "</b>:\n")
                 for oid, htp, hid in oplst[k]:
                     self.add_info("  " + self.fmt_hl_obj_scene(oid) + 
-                      " on " + fmt_opcode(htp) + 
-                      " #{}".format(hid) + fmt_cmt(" // " + 
+                      " on " + self.fmt_opcode(htp) + 
+                      " #{}".format(hid) + self.fmt_cmt(" // " + 
                       self.fmt_hl_obj_scene(oid, True)) + "\n")
 
             # enter areas
@@ -1496,9 +1524,9 @@ class App(tkinter.Frame):
             self.add_info("<b>Dialog handlers<b>: {}\n".format(len(grp.acts)))
             for idx, act in enumerate(grp.acts):
                 self.add_info("  {}) <u>on {} {} 0x{:X} 0x{:X}</u>, dlgs: "\
-                    "{}{}\n".format(idx, fmt_opcode(act.opcode), 
+                    "{}{}\n".format(idx, self.fmt_opcode(act.opcode), 
                         self.fmt_hl_obj(act.ref), act.arg1, act.arg2, \
-                        len(act.dlgs), fmt_cmt(" // " + 
+                        len(act.dlgs), self.fmt_cmt(" // " + 
                             self.fmt_hl_obj(act.ref, True))))
                 for didx, dlg in enumerate(act.dlgs):
                     self.add_info("    {}) <i>0x{:X} 0x{:X}</i>, ops: {}\n".\
@@ -1517,18 +1545,18 @@ class App(tkinter.Frame):
                     for oidx, op in enumerate(dlg.ops):
                         cmt = ""
                         opref = "0x{:X}".format(op.ref)
-                        opcode = fmt_dlgop(op.opcode)
+                        opcode = self.fmt_dlgop(op.opcode)
                         if op.pos in usedadr:
                             self.add_info("      <i>label_{:X}:</i>\n".format(
                                 op.pos))
                         if op.opcode == 0x1: # BREAK
                             if op.pos in usedcase:
                                 if len(usedadr) > 0:
-                                    cmt = fmt_cmt(" // end select <i>"\
+                                    cmt = self.fmt_cmt(" // end select <i>"\
                                         "label_{:X}</i>, case=0x{:}"\
                                         "".format(*usedcase[op.pos]))
                                 else:
-                                    cmt = fmt_cmt(" // end "\
+                                    cmt = self.fmt_cmt(" // end "\
                                         "select case=0x{:}".\
                                         format(usedcase[op.pos][1]))
                         elif op.opcode == 0x2 or op.opcode == 0x8: # MENU or CIRCLE
@@ -1553,12 +1581,12 @@ class App(tkinter.Frame):
                                 else:
                                     docurr = ["complex"]
                             if len(doarr) < sellen:
-                                cmt = fmt_cmt(" // {} select broken, "\
+                                cmt = " // {} select broken, "\
                                     "required={}, got={}".\
-                                    format(opcode, sellen, len(doarr)))
+                                    format(opcode, sellen, len(doarr))
                             else:
                                 cmt += ",".join(["+".join(x) for x in doarr])
-                            cmt = fmt_cmt(cmt)
+                            cmt = self.fmt_cmt(cmt)
                             if menuactstart is not None:
                                 for oidx2, op2 in enumerate(dlg.ops[\
                                         menuactstart:menuactstart + sellen]):
@@ -1567,7 +1595,7 @@ class App(tkinter.Frame):
                             op.opcode == 0x4: # GOTO or MENURET
                             opref = "<i>label_{:X}</i>".format(op.ref)
                             if op.pos in usedmenu:
-                                cmt = fmt_cmt(" // action menu=<i>"\
+                                cmt = self.fmt_cmt(" // action menu=<i>"\
                                     "label_{:X}</i>, case=0x{:}".\
                                     format(*usedmenu[op.pos]))
                         elif op.opcode == 0x7:
@@ -1575,7 +1603,7 @@ class App(tkinter.Frame):
                             if op.msg:
                                 opref = self.fmt_hl_msg(op.ref)
                                 objref = self.fmt_hl_obj(op.msg.obj.idx)
-                                cmt = fmt_cmt(" // obj={}, msg={}".\
+                                cmt = self.fmt_cmt(" // obj={}, msg={}".\
                                     format(objref, 
                                         self.fmt_hl_msg(op.ref, True)))
                         
@@ -1820,8 +1848,7 @@ class App(tkinter.Frame):
                 used.append(text)
                 ts = text
                 if tlt:
-                    ts = transliterate.utils.translit(text, "ru", \
-                        reversed = True)
+                    ts = translit(text)
                 entry = polib.POEntry(
                     msgid = text, msgstr = ts, comment = cmt)
                 po.append(entry)                
@@ -1876,6 +1903,8 @@ class App(tkinter.Frame):
                 format(hlesc(fn), hlesc(traceback.format_exc())))
 
 def main():
+    print(translit("ЛЕСНАЯ ДОРОГА"))
+    #return
     root = tkinter.Tk()
     app = App(master = root)
     argv = sys.argv[1:]


Commit: 01173a0031a00d6689cf00df1154bd864aa20749
    https://github.com/scummvm/scummvm-tools/commit/01173a0031a00d6689cf00df1154bd864aa20749
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: release 0.2n; remove tranliterate library

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 2821de8ba..2a950128e 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1903,8 +1903,6 @@ class App(tkinter.Frame):
                 format(hlesc(fn), hlesc(traceback.format_exc())))
 
 def main():
-    print(translit("ЛЕСНАЯ ДОРОГА"))
-    #return
     root = tkinter.Tk()
     app = App(master = root)
     argv = sys.argv[1:]


Commit: 740c900e522559f56bf0212ae5470a29f1b90ba6
    https://github.com/scummvm/scummvm-tools/commit/740c900e522559f56bf0212ae5470a29f1b90ba6
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: release 0.2l

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 2a950128e..3ae4a3dcf 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -28,7 +28,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2m 2014-05-21"
+VERSION = "v0.2l 2014-05-21"
 
 def hlesc(value):
     if value is None:


Commit: f0d14889aa30338d1d8b2ce1b8b3bda5a53b1e74
    https://github.com/scummvm/scummvm-tools/commit/f0d14889aa30338d1d8b2ce1b8b3bda5a53b1e74
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fix version

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 3ae4a3dcf..b1bc41a06 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -28,7 +28,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2l 2014-05-21"
+VERSION = "v0.2n 2014-05-21"
 
 def hlesc(value):
     if value is None:


Commit: 80f87766e10e561e05eb4653c29756e9cbca7c01
    https://github.com/scummvm/scummvm-tools/commit/80f87766e10e561e05eb4653c29756e9cbca7c01
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fix resource opening

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/help/index.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 92a9d4c2e..81d630548 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,9 +1,13 @@
 Что нового
 ==========
 
+2014-05-23 версия 0.2o
+----------------------
+Исправление ошибки при открытии ресурса.
+
 2014-05-21 версия 0.2n
 ----------------------
-Функуция транслитерации заменена на собсвенную.
+Функуция транслитерации заменена на собственную.
 
 2014-05-21 версия 0.2m
 ----------------------
diff --git a/engines/petka/help/index.txt b/engines/petka/help/index.txt
index 8ae290bd5..ee2fe76ec 100644
--- a/engines/petka/help/index.txt
+++ b/engines/petka/help/index.txt
@@ -1,6 +1,6 @@
 Справка
 
-Версия: 0.2l 2014-05-20
+Версия: 0.2o 2014-05-20
 
  * <a href="/help/changes">Что нового</a>
  * <a href="/help/faq">Часто задаваемые вопросы</a>
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index b1bc41a06..a16133823 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -28,7 +28,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2n 2014-05-21"
+VERSION = "v0.2o 2014-05-23"
 
 def hlesc(value):
     if value is None:
@@ -1018,7 +1018,7 @@ class App(tkinter.Frame):
                     ru = False
                     for act in rec.acts:
                         if ru: break
-                        for op in ops:
+                        for op in act.ops:
                             if res_id == op.op_arg1:
                                 self.add_info("  " + 
                                     self.fmt_hl_obj_scene(rec.idx, True) + "\n")


Commit: 73ead29a6469c0a0aebe97e3421f207d362e6a41
    https://github.com/scummvm/scummvm-tools/commit/73ead29a6469c0a0aebe97e3421f207d362e6a41
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Fix translit Capital cyrillic E

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index a16133823..603659665 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -52,7 +52,7 @@ def fmt_arg(value):
     
 def translit(text):
     ru = "абвгдеёзийклмнопрстуфхъыьэАБВГДЕЁЗИЙКЛМНОПРСТУФХЪЫЬЭ"
-    en = "abvgdeezijklmnoprstufh'y'eABVGDEEZIJKLMNOPRSTUFH'Y'Э"
+    en = "abvgdeezijklmnoprstufh'y'eABVGDEEZIJKLMNOPRSTUFH'Y'E"
     sl = {
         "ж": "zh",
         "ц": "ts",


Commit: d81b65fe32790b0ace4623ae12eeb03596a27543
    https://github.com/scummvm/scummvm-tools/commit/d81b65fe32790b0ace4623ae12eeb03596a27543
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Standalone decompiler for SCRIPT.DAT

Changed paths:
  A engines/petka/p12script.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12script.py b/engines/petka/p12script.py
new file mode 100755
index 000000000..380e5cda9
--- /dev/null
+++ b/engines/petka/p12script.py
@@ -0,0 +1,282 @@
+#! /usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# romiq.kh at gmail.com, 2014
+
+import os, sys
+import traceback
+import argparse
+import io
+import hashlib
+
+import petka
+
+APPNAME = "P1&2 Compiler and decompiler"
+VERSION = "v0.3 2014-06-01"
+
+def find_in_folder(folder, name, ifnot = True):
+    for item in os.listdir(folder):
+        if item.upper() == name.upper():
+            return os.path.join(folder, item)
+    if ifnot:
+        return os.path.join(folder, name)
+    else:
+        return None
+
+# ===========================================================================
+# decompile utils
+# ===========================================================================
+def fmtnum16(num):
+    if num < 10:
+        return "{}".format(num)
+    elif num == 0xffff:
+        return "-1"
+    else:
+        return "0x{:x}".format(num)
+
+def fmtnum32(num):
+    if num < 10:
+        return "{}".format(num)
+    elif num == 0xffffffff:
+        return "-1"
+    else:
+        return "0x{:x}".format(num)
+
+def fmtop(num):
+    if num in petka.OPCODES:
+        return petka.OPCODES[num][0]
+    return "0x{:x}".format(num)
+
+def fmtdlgop(num):
+    if num in petka.DLGOPS:
+        return petka.DLGOPS[num][0]
+    return "0x{:x}".format(num)
+
+def escstr(value):
+    return value.replace("\\", "\\\\").replace("\"", "\\\"")
+
+# ===========================================================================
+# decompile SCRIPT.DAT
+# ===========================================================================
+def pretty_print_scr(pe, name, stream, enc = None, decsort = False):
+    def pprint(msg):
+        if stream is None:
+            print(msg)
+        else:
+            stream.write((msg + "\n").encode(enc or "UTF-8"))
+
+    # define lists of used items
+    used_obj = []
+    used_res = []
+
+    def fmtfor(num, objid):
+        if num == objid:
+            return "THIS"
+        if num in pe.obj_idx:
+            return "obj_{}".format(num)
+        if num in pe.scn_idx:
+            return "scene_{}".format(num)
+        return fmtnum16(num)
+    
+    def printres(resid):
+        pprint("RES res_{} 0x{:x} \"{}\"".format(resid, resid,
+            escstr(pe.res[resid])))
+        pprint("")
+
+    def printrescheck(resid, opcode):
+        tp = petka.OPCODES.get(opcode, ("", 0))[1]
+        if tp == 1 and resid in pe.res:
+            if resid in used_res:
+                return
+            used_res.append(resid)
+            printres(resid)
+
+    def printitem(item, itemtype):
+        pprint("{} {}_{} 0x{:x} \"{}\"".format(itemtype.upper(), itemtype,
+            item.idx, item.idx, escstr(item.name)))
+            
+        # sub objects
+        if itemtype == "scene":
+            if len(item.refs) == 0:
+                pprint("  ZEROREF")
+            for obj, a1, a2, a3, a4, a5 in item.refs:
+                if obj.idx in pe.obj_idx:
+                    ref = "obj_{}".format(obj.idx)
+                elif obj.idx in pe.scn_idx:
+                    ref = "scene_".format(obj.idx)
+                else:
+                    pprint("  # unknown reference to 0x{:x}".format(
+                       obj.idx))
+                    ref = "0x{:x}".format(obj.idx)
+                pprint("  REF {} {} {} {} {} {}".format(ref, fmtnum32(a1), 
+                    fmtnum32(a2), fmtnum32(a3),
+                    fmtnum32(a4), fmtnum32(a5)))
+        
+        for act in item.acts:
+            actif = ""
+            if act.act_status != 0xff or act.act_ref != 0xffff:
+                actif = " 0x{:02x} ".format(act.act_status)
+                if act.act_ref == item.idx:
+                    actif += "THIS"
+                else:
+                    actif += fmtnum16(act.act_ref)
+            pprint("  ON {}{}".format(fmtop(act.act_op), actif))
+            # list actions
+            for op in act.ops:
+                if op.op_arg1 in pe.res and \
+                        petka.OPCODES.get(op.op_code, ["", 0])[1] == 1:
+                    res = "res_{}".format(op.op_arg1)
+                else:
+                    res = fmtnum16(op.op_arg1)
+                pprint("    {} {} {} {} {}".format(fmtop(op.op_code),
+                    fmtfor(op.op_ref, item.idx), res,
+                    fmtnum16(op.op_arg2),
+                    fmtnum16(op.op_arg3)))
+            pprint("  ENDON")
+    
+        pprint("END{} # {}_{}".format(itemtype.upper(), itemtype, item.idx))
+        pprint("")
+        
+    pprint("# Decompile SCRIPT \"{}\"".format(name))
+    pprint("# Version: {}".format(VERSION))
+    pprint("# Encoding: {}".format(enc))
+    
+    if decsort:
+        for idx, scene in enumerate(pe.scenes):
+            pprint("# Scene {} / {}".format(idx + 1, len(pe.scenes)))
+            # display used objects
+            if len(scene.idx.refs) > 0:
+                pprint("# referenced objects {}:".format(len(scene.refs)))
+                for ref in scene.refs:
+                    if ref[0].idx in used_obj: 
+                        pprint("# object 0x{:x} already defined".\
+                            format(ref[0].idx))
+                        continue
+                    used_obj.append(ref[0].idx)
+                    if ref[0].idx in pe.obj_idx:
+                        for act in ref[0].acts_array:
+                            for op in act.ops_array:
+                                printrescheck(op.op_arg1, op.op_code)                
+                        printitem(obj, "obj")                
+            else:        
+                pprint("# No referenced objects")
+            # display res
+            for act in scene.acts:
+                for op in act.ops:
+                    printrescheck(op.op_arg1, op.op_code)                
+            printitem(scene, "scene")
+        # list unused
+        msg = False
+        for obj in pe.objects:
+            if obj.idx in used_obj: continue
+            if not msg:
+                pprint("# Note: Following objects not listed anywhere")
+                msg = True
+            printitem(obj, "OBJ")
+        msg = False
+        for res in self.res:
+            if res in used_res: continue
+            if not msg:
+                pprint("# Note: Following resources not listed anywhere")
+                msg = True
+            printres(res)
+    else:
+        for obj in pe.objects:
+            printitem(obj, "obj")
+        for scene in pe.scenes:
+            printitem(scene, "scene")
+        for res in pe.resord:
+            printres(res)
+
+# check if file already exists and flag for overwrite not set
+def ckeckoverwrite(fn, args):
+    if os.path.exists(fn) and not args.fo:
+        print("File \"{}\" already exists, use -fo to overwrite".\
+            format(fn))
+        return True
+    return False
+
+# check files are the same
+def checksame(f1, n1, f2, n2):
+    if os.path.abspath(f1) == os.path.abspath(f2):
+        print("Error: {} and {} are the same \"{}\"".\
+            format(f1))
+        return True
+    return False
+       
+def action_dec(args):
+    print("Decompile SCRIPT.DAT file")
+    destpath = args.destpath
+    encoding = args.encoding
+    
+    if destpath:
+        if ckeckoverwrite(destpath, args): return -1
+        if checksame(args.sourcepath, "source", destpath, "destination"): 
+            return -2
+        if not encoding:
+            encoding = "UTF-8"
+        
+    print("Input:\t{}".format(args.sourcepath))
+    print("Output:\t{}".format(destpath or "-"))
+    if destpath:
+        print("Enc:\t{}".format(encoding))
+    if args.decompile_sorted:
+        print("Flag decompile_sorted enabled")
+    
+    pe = petka.Engine()
+    pe.init_empty("cp1251")
+    bkgname = find_in_folder(os.path.dirname(args.sourcepath), "backgrnd.bg")
+    resname = find_in_folder(os.path.dirname(args.sourcepath), "resource.qrc")
+    pe.load_script(args.sourcepath, bkgname, resname)
+    if destpath:
+        f = open(destpath, "wb")
+        try:
+            pretty_print_scr(pe, args.sourcepath, f, enc = encoding, \
+                decsort = args.decompile_sorted)
+        finally:
+            f.close()
+    else:
+        pretty_print_scr(pe, args.sourcepath, None)
+
+def action_version(args):
+    print("Version: " + VERSION)
+    
+def main():
+    print(APPNAME + ", " + VERSION)
+    print("\tRoman Kharin (romiq.kh at gmail.com)")
+    if len(sys.argv) < 2:
+        print("Use -h for help.")
+        return
+        
+    if len(sys.argv) >= 3:
+        if sys.argv[1] == "test":
+            internaltest(sys.argv[2])
+            return
+    
+    parser = argparse.ArgumentParser(epilog = \
+        "For actions help try: <action> -h")
+    subparsers = parser.add_subparsers(title = 'actions')
+    
+    # decompile - <script.dat> [[--enc <encoding>] -o <decompiled.txt>]
+    parser_dec = subparsers.add_parser("decompile", aliases = ['d'], \
+        help = "decompile script.dat")
+    parser_dec.add_argument('-fo', action = 'store_true', \
+        help = "force overwrite existing output file")
+    parser_dec.add_argument('--decompile-sorted', action = 'store_true', \
+        help = "display objects and scenes in sorted way (can change order)")
+    parser_dec.add_argument('-o', action = 'store', dest = "destpath",\
+        help = "output path for decompiled (default: stdout)")
+    parser_dec.add_argument('-e', "--enc", action = 'store', dest = "encoding",\
+        help = "output encoding (default: UTF-8)")
+    parser_dec.add_argument('sourcepath', help = "path to SCRIPT.DAT file")
+    parser_dec.set_defaults(func = action_dec)
+
+    # version
+    parser_version = subparsers.add_parser("version", help = "program version")
+    parser_version.set_defaults(func = action_version)
+
+    args = parser.parse_args()
+    args.func(args)
+
+if __name__ == "__main__":
+    main()
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 4fe0a9af2..551b3f243 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -158,6 +158,8 @@ class Engine:
         self.curr_speech = None
         self.curr_diskid = None
         
+    def init_empty(self, enc):
+        self.enc = enc
         
     def parse_ini(self, f):
         # parse ini settings
@@ -205,8 +207,8 @@ class Engine:
         return res, resord
         
     def load_data(self, folder, enc):
+        self.init_empty(enc)
         self.fman = FileManager(folder)
-        self.enc = enc
         # load PARTS.INI
         pf = self.fman.find_path("parts.ini")
         if pf:
@@ -302,16 +304,26 @@ class Engine:
         # load dialogs
         self.load_dialogs()
         
-    def load_script(self):
+    def load_script(self, scrname = None, bkgname = None, resname = None):
         self.objects = []
         self.scenes = []
         self.obj_idx = {}
         self.scn_idx = {}
-
-        try:
-            data = self.fman.read_file(self.curr_path + "script.dat")
-        except:
-            raise EngineError("Can't open SCRIPT.DAT")
+        
+        if scrname is None:
+            try:
+                data = self.fman.read_file(self.curr_path + "script.dat")
+            except:
+                raise EngineError("Can't open SCRIPT.DAT")
+        else:
+            try:
+                f = open(scrname, "rb")
+            except:
+                raise EngineError("Can't open SCRIPT.DAT")
+            try:
+                data = f.read()
+            finally:
+                f.close()
         num_obj, num_scn = struct.unpack_from("<II", data[:8])
         off = 8
         def read_rec(off):
@@ -348,8 +360,25 @@ class Engine:
             self.scenes.append(scn)
             self.scn_idx[scn.idx] = scn
             
-        data = self.fman.read_file(self.curr_path + "backgrnd.bg")
-        num_rec = struct.unpack_from("<I", data[:4])[0]
+        if bkgname is None:
+            try:    
+                data = self.fman.read_file(self.curr_path + "backgrnd.bg")
+            except:
+                data = None
+        else:
+            try:
+                f = open(bkgname, "rb")
+            except:
+                data = None
+            try:
+                data = f.read()
+            finally:
+                f.close()
+
+        if data:        
+            num_rec = struct.unpack_from("<I", data[:4])[0]
+        else:
+            num_rec = 0
         off = 4
         for i in range(num_rec):
             scn_ref, num_ref = struct.unpack_from("<HI", data[off:off + 6])
@@ -371,9 +400,25 @@ class Engine:
                     raise EngineError("DEBUG: Scene ref 0x{:x} not found".\
                         format(obj[0]))
                         
-        f = self.fman.read_file_stream(self.curr_path + "resource.qrc")
-        self.res, self.resord = self.parse_res(f)
-        f.close()
+        if resname is None:
+            try:
+                f = self.fman.read_file_stream(self.curr_path + "resource.qrc")
+            except:
+                f = None
+        else:
+            try:
+                f = open(resname, "rb")
+            except:
+                f = None
+        try:            
+            if f:            
+                self.res, self.resord = self.parse_res(f)
+            else:
+                self.res = {}
+                self.resord = []
+        finally:
+            if f:
+                f.close()
         
     def load_names(self):
         self.names = {}


Commit: 668a70651719f24ba4be0bf909aeeaf200349dfd
    https://github.com/scummvm/scummvm-tools/commit/668a70651719f24ba4be0bf909aeeaf200349dfd
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Compiler for SCRIPT.DAT

Changed paths:
    engines/petka/p12script.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12script.py b/engines/petka/p12script.py
index 380e5cda9..2325e729b 100755
--- a/engines/petka/p12script.py
+++ b/engines/petka/p12script.py
@@ -10,10 +10,13 @@ import io
 import hashlib
 
 import petka
+import petka.engine
 
 APPNAME = "P1&2 Compiler and decompiler"
 VERSION = "v0.3 2014-06-01"
 
+class ScriptSyntaxError(Exception): pass
+
 def find_in_folder(folder, name, ifnot = True):
     for item in os.listdir(folder):
         if item.upper() == name.upper():
@@ -23,170 +26,656 @@ def find_in_folder(folder, name, ifnot = True):
     else:
         return None
 
-# ===========================================================================
-# decompile utils
-# ===========================================================================
-def fmtnum16(num):
-    if num < 10:
-        return "{}".format(num)
-    elif num == 0xffff:
-        return "-1"
-    else:
-        return "0x{:x}".format(num)
+class P12Compiler:
+    def __init__(self):
+        pass
+        
+    # =======================================================================
+    # compiler utils
+    # =======================================================================
 
-def fmtnum32(num):
-    if num < 10:
-        return "{}".format(num)
-    elif num == 0xffffffff:
-        return "-1"
-    else:
-        return "0x{:x}".format(num)
+    def tokenizer(self, source, enc):
+        for lineno, line in enumerate(source.readlines(), 1):
+            line = line.decode(enc or "UTF-8").strip()
+            # eliminate comment
+            nline = []
+            nmode = 0 # 0 - common, 2 - in string
+            nitem = ""
+            nitemesc = False
+            for ch in line:
+                if nmode == 1:
+                    if nitemesc:
+                        nitem += ch
+                        nitemesc = False
+                    else:
+                        if ch == "\\":
+                            # esc-sequence
+                            nitemesc = True
+                            continue
+                        elif ch == "\"":
+                            nitem += ch
+                            nmode = 0
+                            if nitem[:1] == nitem[-1:] == "\"":
+                                nitem = nitem[1:-1]
+                            nline.append(nitem)
+                            nitem = ""
+                            nitemesc = False
+                            continue
+                        nitem += ch
+                else:    
+                    if ch == "\"":
+                        nmode = 1
+                    elif ch == "#":
+                        break
+                    elif ch == " " or ch == "\t":
+                        if len(nitem) > 0:
+                            nline.append(nitem)
+                            nitem = ""
+                        continue
+                    nitem += ch
+
+            if len(nitem) > 0:
+                nline.append(nitem)
+                
+            yield lineno, nline
+        
+
+    def check8(self, value, name, lineno):
+        if value == -1:
+            return 0xff
+        if value < 0:
+            raise ScriptSyntaxError("Error at {}: value for \"{}\" too "\
+                "small - {}".format(lineno, name, value))
+        if value > 0xff:
+            raise ScriptSyntaxError("Error at {}: value for \"{}\" too "\
+                "big - {} > 0xff".format(lineno, name, value))
+        return value
+    
+    def check16(self, value, name, lineno):
+        if value == -1:
+            return 0xffff
+        if value < 0:
+            raise ScriptSyntaxError("Error at {}: value for \"{}\" too "\
+                "small - {}".format(lineno, name, value))
+        if value > 0xffff:
+            raise ScriptSyntaxError("Error at {}: value for \"{}\" too "\
+                "big - {} > 0xffff".format(lineno, name, value))
+        return value
 
-def fmtop(num):
-    if num in petka.OPCODES:
-        return petka.OPCODES[num][0]
-    return "0x{:x}".format(num)
-
-def fmtdlgop(num):
-    if num in petka.DLGOPS:
-        return petka.DLGOPS[num][0]
-    return "0x{:x}".format(num)
-
-def escstr(value):
-    return value.replace("\\", "\\\\").replace("\"", "\\\"")
-
-# ===========================================================================
-# decompile SCRIPT.DAT
-# ===========================================================================
-def pretty_print_scr(pe, name, stream, enc = None, decsort = False):
-    def pprint(msg):
-        if stream is None:
-            print(msg)
+    def check32(self, value, name, lineno):
+        if value == -1:
+            return 0xffffffff
+        if value < 0:
+            raise ScriptSyntaxError("Error at {}: value for \"{}\" too "\
+                "small - {}".format(lineno, name, value))
+        if value > 0xffffffff:
+            raise ScriptSyntaxError("Error at {}: value for \"{}\" too "\
+                "big - {} > 0xffffffff".format(lineno, name, value))
+        return value
+    
+    def convertnum(self, value):
+        num = None
+        if value[:2].upper() == "0X":
+            try:
+                num = int(value[2:], 16)
+            except:
+                pass
+        else:                            
+            try:
+                num = int(value, 10)
+            except:
+                pass
+        return num
+
+    def checkusedid(self, ident, lineno):
+        if ident in self.usedid:
+            raise ScriptSyntaxError("Error at {}: identificator \"{}\" "\
+                "already used at line {}".format(lineno, ident, \
+                self.usedid[ident][0]))
+        self.usedid[ident] = [lineno, None]
+    
+    def setidentvalue(self, ident, value):
+        if self.usedid[ident][1] is None:
+            self.usedid[ident][1] = value
         else:
-            stream.write((msg + "\n").encode(enc or "UTF-8"))
-
-    # define lists of used items
-    used_obj = []
-    used_res = []
-
-    def fmtfor(num, objid):
-        if num == objid:
-            return "THIS"
-        if num in pe.obj_idx:
-            return "obj_{}".format(num)
-        if num in pe.scn_idx:
-            return "scene_{}".format(num)
-        return fmtnum16(num)
+            raise ScriptSyntaxError("Redefine value for \"{}\" ({}) from "\
+                "\"{}\" to \"{}\"".format(ident, self.usedid[ident][0], \
+                self.usedid[ident][0], value))
     
-    def printres(resid):
-        pprint("RES res_{} 0x{:x} \"{}\"".format(resid, resid,
-            escstr(pe.res[resid])))
-        pprint("")
-
-    def printrescheck(resid, opcode):
-        tp = petka.OPCODES.get(opcode, ("", 0))[1]
-        if tp == 1 and resid in pe.res:
-            if resid in used_res:
-                return
-            used_res.append(resid)
-            printres(resid)
+    def getidentvalue(self, ident):
+        return self.usedid[ident][1]
+    
+    def checkident(self, ident, name, lineno):
+        if ident.upper() in self.reservedid:
+            raise ScriptSyntaxError("Error at {}: identificator \"{}\" "\
+                "in {} forbidden".format(lineno, ident, name))
+        if ident not in self.usedid:
+            raise ScriptSyntaxError("Error at {}: identificator \"{}\" "\
+                "in {} not found".format(lineno, ident, name))
+        if self.usedid[ident][1] is None:
+            raise ScriptSyntaxError("Error at {}: identificator \"{}\" "\
+                "in {} has no value (internal error)".\
+                format(lineno, ident, name))
 
-    def printitem(item, itemtype):
-        pprint("{} {}_{} 0x{:x} \"{}\"".format(itemtype.upper(), itemtype,
-            item.idx, item.idx, escstr(item.name)))
-            
-        # sub objects
-        if itemtype == "scene":
-            if len(item.refs) == 0:
-                pprint("  ZEROREF")
-            for obj, a1, a2, a3, a4, a5 in item.refs:
-                if obj.idx in pe.obj_idx:
-                    ref = "obj_{}".format(obj.idx)
-                elif obj.idx in pe.scn_idx:
-                    ref = "scene_".format(obj.idx)
-                else:
-                    pprint("  # unknown reference to 0x{:x}".format(
-                       obj.idx))
-                    ref = "0x{:x}".format(obj.idx)
-                pprint("  REF {} {} {} {} {} {}".format(ref, fmtnum32(a1), 
-                    fmtnum32(a2), fmtnum32(a3),
-                    fmtnum32(a4), fmtnum32(a5)))
+    def checkstruct(self, name, struct, data, lineno):
+        try:
+            struct.build(data)
+        except Exception as e:
+            msg = "Internal error {}".format(name)
+            if lineno is not None:
+                msg += ", at line {}".format(lineno)
+            raise ScriptSyntaxError(msg + "\n" + str(e))
+
+    def convertargs(self, fmt, args, lineno):
+        # fmt = [(name1, check1, withident),]
+        pargs = []
+        argnum = [None] * len(args)
+        for i, arg in enumerate(args):
+            argnum[i] = self.convertnum(arg)
+            if argnum[i] is None:
+                if not fmt[i][2]:
+                    raise ScriptSyntaxError("Error at {}: bad number for {}".\
+                        format(lineno, fmt[i][0]))
+                self.checkident(arg, fmt[i][0], lineno)
+                argnum[i] = self.getidentvalue(arg)
+            argnum[i] = fmt[i][1](argnum[i], fmt[i][0], lineno)
+        return argnum
+
+    # =======================================================================
+    # compile SCRIPT.DAT
+    # =======================================================================
+
+    def compile_script(self, source, destfolder, enc = None, \
+            st_scr = None, st_bkg = None, st_res = None):
+
+        pe = petka.Engine()
+        pe.init_empty("cp1251")
         
-        for act in item.acts:
-            actif = ""
-            if act.act_status != 0xff or act.act_ref != 0xffff:
-                actif = " 0x{:02x} ".format(act.act_status)
-                if act.act_ref == item.idx:
-                    actif += "THIS"
+        mode = 0 # 0 - common, 1 - object/scene, 2 - on
+        mode1tp = None
+
+        # used identificators
+        self.usedid = {}
+        # compiled resources
+        compres = []
+        # compiled obj
+        compobj = []
+        # compled scenes
+        compscene = []
+        # current item
+        compitem = None
+        # obj/scene used numbers
+        compitemused = {}
+        # current action
+        compact = None
+                   
+        revOPS = {}
+        for ok, ov in petka.OPCODES.items():
+            revOPS[ov[0]] = ok
+        self.reservedid = ["THIS", "OBJ", "ENDOBJ", "SCENE", "ENDSCENE", \
+            "RES", "REF", "ON", "ENDON"] + list(revOPS.keys())
+
+        for lineno, tokens in self.tokenizer(source, enc):
+            if len(tokens) == 0:
+                continue
+            if mode == 0:
+                # accept: RES, OBJ, SCENE
+                cmd = tokens[0].upper()
+                if cmd == "RES":
+                    if len(tokens) != 4:
+                        raise ScriptSyntaxError("Error at {}: RES syntax "\
+                            "error".format(lineno, cmd))
+                    self.checkusedid(tokens[1], lineno)
+                    compres.append((lineno, tokens[1], \
+                        tokens[2], tokens[3]))
+                elif cmd == "OBJ" or cmd == "SCENE":
+                    # object / scene
+                    mode = 1
+                    mode1tp = cmd
+                    # check syntax
+                    if len(tokens) != 4:
+                        raise ScriptSyntaxError("Error at {}: {} syntax "\
+                            "error".format(lineno, cmd))
+                    self.checkusedid(tokens[1], lineno)
+                    num = self.convertnum(tokens[2])
+                    if num is not None:
+                        if num in compitemused:
+                            raise ScriptSyntaxError("Error at {}: {} "\
+                                "number {} already used at {}".format(\
+                                    lineno, cmd.lower(), num, \
+                                    compitemused[num]))
+                        else:
+                            compitemused[num] = lineno
+                        num = self.check16(num, "{} number".format(cmd.lower()),
+                            lineno)
+                        self.setidentvalue(tokens[1], num)
+                    else:
+                        raise ScriptSyntaxError("Error at {}: {} number bad "\
+                            "format \"{}\" ".format(lineno, cmd.lower(),
+                                tokens[2]))
+                    compitem = {"tp": cmd, "lineno": lineno, \
+                        "ident": tokens[1], "num": num, \
+                        "name": tokens[3], "ref": None, "acts": []}
+                else:
+                    raise ScriptSyntaxError("Error at {}: unknown syntax "\
+                        "\"{}\"".format(lineno, cmd))
+            elif mode == 1:                            
+                # accept: REF, ON, ENDOBJ, ENDSCENE
+                cmd = tokens[0].upper()
+                if cmd == "REF" and mode1tp == "SCENE":
+                    if compitem["ref"] is None:
+                        compitem["ref"] = []
+                    # check format
+                    if len(tokens) < 4 or len(tokens) > 7:
+                        raise ScriptSyntaxError("Error at {}: unknown REF "\
+                            "syntax in {}".format(lineno, mode1tp))
+                    while len(tokens) < 7:
+                        tokens.append("-1")
+                    compitem["ref"].append([lineno] + tokens[1:])
+                elif cmd == "ZEROREF" and mode1tp == "SCENE":
+                    # zero rererence at all
+                    if compitem["ref"] is not None:
+                        # can't empty any
+                        raise ScriptSyntaxError("Error at {}: ref already "\
+                            "specified".format(lineno))
+                    compitem["ref"] = []
+                elif cmd == "ON":
+                    # on action
+                    if len(tokens) != 2 and len(tokens) != 4:
+                        raise ScriptSyntaxError("Error at {}: unknown ON "\
+                            "syntax in {}".format(lineno, mode1tp))
+                    son = self.convertnum(tokens[1])
+                    if son is None:
+                        if tokens[1].upper() in revOPS:
+                            son = revOPS[tokens[1].upper()]
+                        else:
+                            raise ScriptSyntaxError("Error at {}: unknown ON "\
+                                "OPREF \"{}\" in {}".\
+                                    format(lineno, tokens[1], mode1tp))
+                    else:
+                        son = self.check16(son, "ON opref", lineno)
+                    if len(tokens) == 2:
+                        tokens += ["-1", "-1"]
+                    status = self.convertnum(tokens[2])
+                    if status is None:
+                        raise ScriptSyntaxError("Error at {}: unknown ON "\
+                            "status \"{}\" in {}".\
+                                format(lineno, tokens[2], mode1tp))
+                    status = self.check8(status, "ON status", lineno)
+                    compact = {"son": son, "ops": [], "status": status, \
+                        "sonref": tokens[3], "lineno": lineno}
+                    mode = 2
+                elif cmd == "END" + mode1tp and len(tokens) == 1:
+                    if mode1tp == "OBJ":
+                        compobj.append(compitem)                        
+                    else:
+                        compscene.append(compitem)                        
+                    compitem = None
+                    # return
+                    mode = 0
                 else:
-                    actif += fmtnum16(act.act_ref)
-            pprint("  ON {}{}".format(fmtop(act.act_op), actif))
-            # list actions
-            for op in act.ops:
-                if op.op_arg1 in pe.res and \
-                        petka.OPCODES.get(op.op_code, ["", 0])[1] == 1:
-                    res = "res_{}".format(op.op_arg1)
+                    raise ScriptSyntaxError("Error at {}: unknown syntax "\
+                        "\"{}\" in {}".\
+                        format(lineno, cmd, mode1tp))
+            elif mode == 2:
+                # accept: ENDON, OPCODE, any number
+                cmd = tokens[0].upper()
+                if cmd == "ENDON" and len(tokens) == 1:
+                    compitem["acts"].append(compact)
+                    compact = None                
+                    mode = 1
                 else:
-                    res = fmtnum16(op.op_arg1)
-                pprint("    {} {} {} {} {}".format(fmtop(op.op_code),
-                    fmtfor(op.op_ref, item.idx), res,
-                    fmtnum16(op.op_arg2),
-                    fmtnum16(op.op_arg3)))
-            pprint("  ENDON")
+                    # check format
+                    if len(tokens) < 2 or len(tokens) > 5:
+                        raise ScriptSyntaxError("Error at {}: unknown OP "\
+                            "syntax in {}".format(lineno, mode1tp))
+                    while len(tokens) < 5:
+                        tokens.append("-1")
+                    op = {}
+                    if cmd in revOPS:
+                        # opcode
+                        opcode = revOPS[cmd]
+                    else:
+                        opcode = self.convertnum(cmd)
+                        if opcode is None:
+                            raise ScriptSyntaxError("Error at {}: unknown OP "\
+                                "\"{}\" in ON".\
+                                format(lineno, cmd))
+                        opcode = self.check16(opcode, "opcode", lineno)
+                    compact["ops"].append({"opcode": opcode, "lineno": lineno, \
+                        "obj_ref": tokens[1], "args": tokens[2:]})
+            else:
+                raise ScriptSyntaxError("Error at {}: unknown parser mode {}".\
+                    format(lineno, mode))
+
+        # check unclosed objects
+        if mode != 0:
+            raise ScriptSyntaxError("Error at {}: unfinished structure".\
+                format(lineno))
     
-        pprint("END{} # {}_{}".format(itemtype.upper(), itemtype, item.idx))
-        pprint("")
+        # second stage - RES
+        # 1. capture res with numbers
+        resused = {}
+        for lineno, ident, resnum, path in compres:
+            num = self.convertnum(resnum)
+            if num is not None:
+                if num in resused:
+                    raise ScriptSyntaxError("Error at {}: RES number {} "\
+                        "already used at {}".format(lineno, num, resused[num]))
+                else:
+                    resused[num] = lineno
+                num = self.check16(num, "RES id", lineno)
+                self.setidentvalue(ident, num)
+            #elif resnum == "_":
+            #    pass
+            else:
+                raise ScriptSyntaxError("Error at {}: RES number bad "\
+                    "format \"{}\" ".format(lineno, resnum))
+        # disabled, unsure about valid numbers range
+        # 2. autonumber rest
+        #autoresnum = 0xfffe
+        #for lineno, ident, resnum, path in compres:
+        #    if resnum == "_":
+        #        while autoresnum > 0:
+        #            num = autoresnum
+        #            if num in resused:
+        #                autoresnum -= 1
+        #                continue
+        #            else:
+        #                self.setidentvalue(ident, num)
+        #                resused[num] = lineno
+        #                break                    
+        #        if autoresnum <= 0:
+        #            raise ScriptSyntaxError("Error at {}: out of resources "\
+        #                "number".format(lineno))
+        # 3. compile
+
+        if destfolder is not None:
+            f = open(os.path.join(destfolder, "resource.qrc"), "wb")
+        else:
+            f = st_res
+        try:
+            def writer(msg):
+                f.write((msg + "\r\n").encode("CP1251"))
+            writer("")
+            for lineno, ident, resnum, path in compres:
+                writer("{:d} == {}".format(self.getidentvalue(ident), path))
+        finally:
+            if destfolder is not None:
+                f.close()
+        print("RESOURCE.QRC saved: {} items".format(len(resused)))
+            
+        # second stage - OBJ
+        def makerec(citem):
+            item = petka.engine.ScrObject(citem["num"], citem["name"])
+            item.acts = []
+            for act in citem["acts"]:
+                if act["sonref"].upper() == "THIS":
+                    sonref = item.idx
+                else:
+                    sonref = self.convertnum(act["sonref"])
+                if sonref is not None:
+                    # direct number
+                    sonref = self.check16(sonref, "ON object ref", 
+                        act["lineno"])
+                else:
+                    # get by ident
+                    self.checkident(act["sonref"], "ON object ref", \
+                        act["lineno"])
+                    sonref = self.getidentvalue(act["sonref"])
+                onrec = petka.engine.ScrActObject(act["son"], 
+                    act["status"], sonref)
+                onrec.ops = []
+                for op in act["ops"]:
+                    # object ref
+                    if op["obj_ref"].upper() == "THIS":
+                        opref = item.idx
+                    else:
+                        opref = self.convertnum(op["obj_ref"])
+                    if opref is not None:
+                        # direct number
+                        opref = self.check16(opref, "OP object", op["lineno"])
+                    else:
+                        # get by ident
+                        self.checkident(op["obj_ref"], "OP object", \
+                            op["lineno"])
+                        opref = self.getidentvalue(op["obj_ref"])
+                    # arguments
+                    fmt = []
+                    for i in range(3):
+                        fmt.append(("OP argument {}".format(i + 1), \
+                            self.check16, True))
+                    argnum = self.convertargs(fmt, op["args"], op["lineno"])
+                    oprec =  petka.engine.ScrOpObject(opref, op["opcode"], 
+                        argnum[0], argnum[1], argnum[2])
+                    onrec.ops.append(oprec)
+                item.acts.append(onrec)
+            return item
+            
+        for citem in compobj:
+            objrec = makerec(citem)
+            pe.objects.append(objrec)
+            pe.obj_idx[objrec.idx] = objrec
+                  
+        # second stage - SCENE
+        backgrnd = []
+        num_bkg = 0
+        for citem in compscene:
+            scenerec = makerec(citem)
+            pe.scenes.append(scenerec)
+
+            if citem["ref"] is not None:
+                scenerec.refs = []
+                num_bkg += 1
+                for ref in citem["ref"]:
+                    fmt = [("REF object", self.check16, True)]
+                    for i in range(5):
+                        fmt.append(("REF argument {}".format(i + 1), \
+                            self.check32, True))
+                    argnum = self.convertargs(fmt, ref[1:], ref[0])
+                    # build REF
+                    if argnum[0] not in pe.obj_idx:
+                        raise ScriptSyntaxError("Error at {}: referenced "
+                            "object 0x{:x} not found".\
+                            format(lineno, argnum[0]))
+                    scenerec.refs.append([pe.obj_idx[argnum[0]], argnum[1],
+                         argnum[2],  argnum[3],  argnum[4],  argnum[5]])
+
+
+        if destfolder is not None:
+            f = open(os.path.join(destfolder, "backgrnd.bg"), "wb")
+        else:
+            f = st_bkg
+        try:
+            pe.write_backgrnd(f)
+        finally:
+            if destfolder is not None:
+                f.close()
+        print("BACKGRND.BG saved: {} items".format(num_bkg))
+
+        if destfolder is not None:
+            f = open(os.path.join(destfolder, "script.dat"), "wb")
+        else:
+            f = st_scr
+        try:
+            pe.write_script(f)
+        finally:
+            if destfolder is not None:
+                f.close()
+        print("SCRIPT.DAT saved: {} objects, {} scenes".\
+            format(len(pe.objects), len(pe.scenes)))
+
+    # =======================================================================
+    # decompile utils
+    # =======================================================================
+    def fmtnum16(self, num):
+        if num < 10:
+            return "{}".format(num)
+        elif num == 0xffff:
+            return "-1"
+        else:
+            return "0x{:x}".format(num)
+
+    def fmtnum32(self, num):
+        if num < 10:
+            return "{}".format(num)
+        elif num == 0xffffffff:
+            return "-1"
+        else:
+            return "0x{:x}".format(num)
+
+    def fmtop(self, num):
+        if num in petka.OPCODES:
+            return petka.OPCODES[num][0]
+        return "0x{:x}".format(num)
+
+    def fmtdlgop(self, num):
+        if num in petka.DLGOPS:
+            return petka.DLGOPS[num][0]
+        return "0x{:x}".format(num)
+
+    def escstr(self, value):
+        return value.replace("\\", "\\\\").replace("\"", "\\\"")
+
+    # =======================================================================
+    # decompile SCRIPT.DAT
+    # =======================================================================
+    def pretty_print_scr(self, scrname, stream, enc = None, decsort = False):
+        def pprint(msg):
+            if stream is None:
+                print(msg)
+            else:
+                stream.write((msg + "\n").encode(enc or "UTF-8"))
+
+        pe = petka.Engine()
+        pe.init_empty("cp1251")
+        bkgname = find_in_folder(os.path.dirname(scrname), "backgrnd.bg")
+        resname = find_in_folder(os.path.dirname(scrname), "resource.qrc")
+        pe.load_script(scrname, bkgname, resname)
+
+        # define lists of used items
+        used_obj = []
+        used_res = []
+
+        def fmtfor(num, objid):
+            if num == objid:
+                return "THIS"
+            if num in pe.obj_idx:
+                return "obj_{}".format(num)
+            if num in pe.scn_idx:
+                return "scene_{}".format(num)
+            return self.fmtnum16(num)
         
-    pprint("# Decompile SCRIPT \"{}\"".format(name))
-    pprint("# Version: {}".format(VERSION))
-    pprint("# Encoding: {}".format(enc))
-    
-    if decsort:
-        for idx, scene in enumerate(pe.scenes):
-            pprint("# Scene {} / {}".format(idx + 1, len(pe.scenes)))
-            # display used objects
-            if len(scene.idx.refs) > 0:
-                pprint("# referenced objects {}:".format(len(scene.refs)))
-                for ref in scene.refs:
-                    if ref[0].idx in used_obj: 
-                        pprint("# object 0x{:x} already defined".\
-                            format(ref[0].idx))
-                        continue
-                    used_obj.append(ref[0].idx)
-                    if ref[0].idx in pe.obj_idx:
-                        for act in ref[0].acts_array:
-                            for op in act.ops_array:
-                                printrescheck(op.op_arg1, op.op_code)                
-                        printitem(obj, "obj")                
-            else:        
-                pprint("# No referenced objects")
-            # display res
-            for act in scene.acts:
+        def printres(resid):
+            pprint("RES res_{} 0x{:x} \"{}\"".format(resid, resid,
+                self.escstr(pe.res[resid])))
+            pprint("")
+
+        def printrescheck(resid, opcode):
+            tp = petka.OPCODES.get(opcode, ("", 0))[1]
+            if tp == 1 and resid in pe.res:
+                if resid in used_res:
+                    return
+                used_res.append(resid)
+                printres(resid)
+
+        def printitem(item, itemtype):
+            pprint("{} {}_{} 0x{:x} \"{}\"".format(itemtype.upper(), itemtype,
+                item.idx, item.idx, self.escstr(item.name)))
+                
+            # sub objects
+            if itemtype == "scene":
+                if len(item.refs) == 0:
+                    pprint("  ZEROREF")
+                for obj, a1, a2, a3, a4, a5 in item.refs:
+                    if obj.idx in pe.obj_idx:
+                        ref = "obj_{}".format(obj.idx)
+                    elif obj.idx in pe.scn_idx:
+                        ref = "scene_".format(obj.idx)
+                    else:
+                        pprint("  # unknown reference to 0x{:x}".format(
+                           obj.idx))
+                        ref = "0x{:x}".format(obj.idx)
+                    pprint("  REF {} {} {} {} {} {}".format(ref, 
+                        self.fmtnum32(a1), self.fmtnum32(a2), 
+                        self.fmtnum32(a3), self.fmtnum32(a4), 
+                        self.fmtnum32(a5)))
+            
+            for act in item.acts:
+                actif = ""
+                if act.act_status != 0xff or act.act_ref != 0xffff:
+                    actif = " 0x{:02x} ".format(act.act_status)
+                    if act.act_ref == item.idx:
+                        actif += "THIS"
+                    else:
+                        actif += self.fmtnum16(act.act_ref)
+                pprint("  ON {}{}".format(self.fmtop(act.act_op), actif))
+                # list actions
                 for op in act.ops:
-                    printrescheck(op.op_arg1, op.op_code)                
-            printitem(scene, "scene")
-        # list unused
-        msg = False
-        for obj in pe.objects:
-            if obj.idx in used_obj: continue
-            if not msg:
-                pprint("# Note: Following objects not listed anywhere")
-                msg = True
-            printitem(obj, "OBJ")
-        msg = False
-        for res in self.res:
-            if res in used_res: continue
-            if not msg:
-                pprint("# Note: Following resources not listed anywhere")
-                msg = True
-            printres(res)
-    else:
-        for obj in pe.objects:
-            printitem(obj, "obj")
-        for scene in pe.scenes:
-            printitem(scene, "scene")
-        for res in pe.resord:
-            printres(res)
+                    if op.op_arg1 in pe.res and \
+                            petka.OPCODES.get(op.op_code, ["", 0])[1] == 1:
+                        res = "res_{}".format(op.op_arg1)
+                    else:
+                        res = self.fmtnum16(op.op_arg1)
+                    pprint("    {} {} {} {} {}".format(self.fmtop(op.op_code),
+                        fmtfor(op.op_ref, item.idx), res,
+                        self.fmtnum16(op.op_arg2),
+                        self.fmtnum16(op.op_arg3)))
+                pprint("  ENDON")
+        
+            pprint("END{} # {}_{}".format(itemtype.upper(), itemtype, item.idx))
+            pprint("")
+            
+        pprint("# Decompile SCRIPT \"{}\"".format(scrname))
+        pprint("# Version: {}".format(VERSION))
+        pprint("# Encoding: {}".format(enc))
+        
+        if decsort:
+            for idx, scene in enumerate(pe.scenes):
+                pprint("# Scene {} / {}".format(idx + 1, len(pe.scenes)))
+                # display used objects
+                if len(scene.idx.refs) > 0:
+                    pprint("# referenced objects {}:".format(len(scene.refs)))
+                    for ref in scene.refs:
+                        if ref[0].idx in used_obj: 
+                            pprint("# object 0x{:x} already defined".\
+                                format(ref[0].idx))
+                            continue
+                        used_obj.append(ref[0].idx)
+                        if ref[0].idx in pe.obj_idx:
+                            for act in ref[0].acts_array:
+                                for op in act.ops_array:
+                                    printrescheck(op.op_arg1, op.op_code)                
+                            printitem(obj, "obj")                
+                else:        
+                    pprint("# No referenced objects")
+                # display res
+                for act in scene.acts:
+                    for op in act.ops:
+                        printrescheck(op.op_arg1, op.op_code)                
+                printitem(scene, "scene")
+            # list unused
+            msg = False
+            for obj in pe.objects:
+                if obj.idx in used_obj: continue
+                if not msg:
+                    pprint("# Note: Following objects not listed anywhere")
+                    msg = True
+                printitem(obj, "OBJ")
+            msg = False
+            for res in self.res:
+                if res in used_res: continue
+                if not msg:
+                    pprint("# Note: Following resources not listed anywhere")
+                    msg = True
+                printres(res)
+        else:
+            for obj in pe.objects:
+                printitem(obj, "obj")
+            for scene in pe.scenes:
+                printitem(scene, "scene")
+            for res in pe.resord:
+                printres(res)
 
 # check if file already exists and flag for overwrite not set
 def ckeckoverwrite(fn, args):
@@ -204,6 +693,7 @@ def checksame(f1, n1, f2, n2):
         return True
     return False
        
+       
 def action_dec(args):
     print("Decompile SCRIPT.DAT file")
     destpath = args.destpath
@@ -223,20 +713,89 @@ def action_dec(args):
     if args.decompile_sorted:
         print("Flag decompile_sorted enabled")
     
-    pe = petka.Engine()
-    pe.init_empty("cp1251")
-    bkgname = find_in_folder(os.path.dirname(args.sourcepath), "backgrnd.bg")
-    resname = find_in_folder(os.path.dirname(args.sourcepath), "resource.qrc")
-    pe.load_script(args.sourcepath, bkgname, resname)
+    dcs = P12Compiler()
     if destpath:
         f = open(destpath, "wb")
         try:
-            pretty_print_scr(pe, args.sourcepath, f, enc = encoding, \
+            dcs.pretty_print_scr(args.sourcepath, f, enc = encoding, \
                 decsort = args.decompile_sorted)
         finally:
             f.close()
     else:
-        pretty_print_scr(pe, args.sourcepath, None)
+        dcs.pretty_print_scr(args.sourcepath, None)
+
+def action_comp(args):
+    print("Compile SCRIPT.DAT file")
+    print("Input:\t{}".format(args.sourcepath))
+    print("Output:\t{}".format(args.destfolder))
+    print("Enc:\t{}".format(args.encoding or "UTF-8"))
+
+    dcs = P12Compiler()
+    if os.path.exists(args.destfolder) and not args.fo:
+        cnt = 0
+        lst = ["script.dat", "backgrnd.bg", "resource.qrc"]
+        for item in lst:
+            if find_in_folder(args.destfolder, item, False):
+                print("Error: destination file \"{}\" already "\
+                    "exists, use -fo to overwrite".format(item))
+                return
+
+    if not os.path.exists(args.destfolder):
+        os.makedirs(args.destfolder)
+    f = open(args.sourcepath, "rb")
+    try:
+        dcs.compile_script(f, args.destfolder, args.encoding)
+    except ScriptSyntaxError as e:
+        if args.trace_error:
+            traceback.print_exc()
+        print(e, file = sys.stderr)
+    finally:
+        f.close()
+
+def internaltest(folder):
+    # unpack testdata.7z
+    test_arr = [
+      "p1demo",
+      "p1-0", "p1-1", "p1-2", "p1-3",
+      "p2-0", "p2-1", "p2-2"
+    ]
+    def compare(fn, mem):
+        f = open(fn, "rb")
+        hf = hashlib.md5()
+        try:
+            hf.update(f.read())
+        finally:
+            f.close()
+        mem.seek(0)
+        hm = hashlib.md5()
+        hm.update(mem.read())
+        return hm.hexdigest() == hf.hexdigest()
+            
+    for test in test_arr:
+        print("=== Test: " + test + " ===")
+        testbase = os.path.join(folder, test)
+        path = find_in_folder(testbase, "script.dat")
+        dcs = P12Compiler()
+        mems = io.BytesIO()        
+        dcs.pretty_print_scr(path, mems)
+        print("Decompiled script:", mems.tell())
+        # compile back
+        memscr = io.BytesIO()
+        membkg = io.BytesIO()
+        memres = io.BytesIO()
+        mems.seek(0)
+        ndcs = P12Compiler()
+        ndcs.compile_script(mems, None, None, \
+            memscr, membkg, memres)
+        # compare
+        if not compare(path, memscr):
+            print("SCRIPT.DAT - mismatch")
+            break
+        if not compare(find_in_folder(testbase, "backgrnd.bg"), membkg):
+            print("BACKGRND.BG - mismatch")
+            break
+
+        print("All ok")
 
 def action_version(args):
     print("Version: " + VERSION)
@@ -271,6 +830,19 @@ def main():
     parser_dec.add_argument('sourcepath', help = "path to SCRIPT.DAT file")
     parser_dec.set_defaults(func = action_dec)
 
+    # compile - <source.txt> <destination folder> [--enc <encoding>]
+    parser_comp = subparsers.add_parser("compile", aliases = ['c'], \
+        help = "compile script.dat")
+    parser_comp.add_argument('-fo', action = 'store_true', \
+        help = "force overwrite existing output files")
+    parser_comp.add_argument('-e', "--enc", action = 'store', \
+        dest = "encoding", help = "output encoding (default: UTF-8)")
+    parser_comp.add_argument('-te', "--trace-error", action = 'store_true', \
+        help = "trace syntax error")
+    parser_comp.add_argument('sourcepath', help = "path to SOURCE.TXT file")
+    parser_comp.add_argument('destfolder', help = "path to output folder")
+    parser_comp.set_defaults(func = action_comp)
+
     # version
     parser_version = subparsers.add_parser("version", help = "program version")
     parser_version.set_defaults(func = action_version)
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 551b3f243..5fef532f8 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -160,6 +160,10 @@ class Engine:
         
     def init_empty(self, enc):
         self.enc = enc
+        self.objects = []
+        self.scenes = []
+        self.obj_idx = {}
+        self.scn_idx = {}
         
     def parse_ini(self, f):
         # parse ini settings
@@ -555,4 +559,32 @@ class Engine:
                     dlg.ops = oparr
             finally:
                 f.close()
+                
+    def write_script(self, f):
+        f.write(struct.pack("<II", len(self.objects), len(self.scenes)))
+
+        def write_rec(rec):
+            ename = rec.name.encode(self.enc)
+            f.write(struct.pack("<HI", rec.idx, len(ename)))
+            f.write(ename)
+            f.write(struct.pack("<I", len(rec.acts)))
+            for act in rec.acts:
+                f.write(struct.pack("<HBHI", act.act_op, act.act_status, 
+                    act.act_ref, len(act.ops)))
+                for op in act.ops:
+                    f.write(struct.pack("<5H", op.op_ref, op.op_code,
+                        op.op_arg1, op.op_arg2, op.op_arg3))
+        
+        for rec in self.objects + self.scenes:
+            write_rec(rec)
+        
+    def write_backgrnd(self, f):
+        lst = [scn for scn in self.scenes if scn.refs is not None]
+        f.write(struct.pack("<I", len(lst)))
+        for scn in lst:
+            f.write(struct.pack("<HI", scn.idx, len(scn.refs)))
+            for ref in scn.refs:
+                f.write(struct.pack("<H5I", ref[0].idx, ref[1], ref[2], 
+                    ref[3], ref[4], ref[5]))
+                
 


Commit: c4a8a5035025f57e04957161eb6c49922cbd5b01
    https://github.com/scummvm/scummvm-tools/commit/c4a8a5035025f57e04957161eb6c49922cbd5b01
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Decompiler for DIALOGUE.FIX and .LOD

Changed paths:
    engines/petka/dist/setup.py
    engines/petka/p12script.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/dist/setup.py b/engines/petka/dist/setup.py
index fb31ef8dc..10d321aa4 100644
--- a/engines/petka/dist/setup.py
+++ b/engines/petka/dist/setup.py
@@ -17,11 +17,14 @@ executables = [
     Executable('p12explore.py',
         base = 'Win32GUI',
         targetName = "p12explore.exe")
+    Executable('p12script.py',
+        base = 'Console',
+        targetName = "p12script.exe")
 ]
 
 setup(name='p12explore',
-      version = '0.2',
-      description = 'Petka 1&2 explorer',
+      version = '0.3',
+      description = 'Petka 1&2 utilities',
       author = "romiq.kh at gmail.com, https://bitbucket.org/romiq/p12simtran",
       options = dict(build_exe = buildOptions),
       executables = executables)
diff --git a/engines/petka/p12script.py b/engines/petka/p12script.py
index 2325e729b..a8f161889 100755
--- a/engines/petka/p12script.py
+++ b/engines/petka/p12script.py
@@ -677,6 +677,133 @@ class P12Compiler:
             for res in pe.resord:
                 printres(res)
 
+    # =======================================================================
+    # decompile DIALOGUE.FIX
+    # =======================================================================
+    def pretty_print_dlg(self, fixname, stream, enc = None, verbose = False):
+        def pprint(msg):
+            if stream is None:
+                print(msg)
+            else:
+                stream.write((msg + "\n").encode(enc or "UTF-8"))
+
+        pe = petka.Engine()
+        pe.init_empty("cp1251")
+        lodname = find_in_folder(os.path.dirname(fixname), "dialogue.lod")
+        pe.load_dialogs(fixname, lodname, True)
+
+        pprint("# Decompile DIALOGUE \"{}\"".format(fixname))
+        pprint("# Version: {}".format(VERSION))
+        pprint("# Encoding: {}".format(enc))
+
+        for msg in pe.msgs:
+            pprint("# {} = 0x{:x}".format(msg.idx, msg.idx))
+            pprint("MSG msg_{} \"{}\" 0x{:x} 0x{:x} 0x{:x}".format(\
+                msg.idx, msg.wav, msg.arg1, msg.arg2, msg.arg3))
+            pprint(" \"{}\"".format(self.escstr(msg.name)))
+            pprint("")
+    
+        for gidx, grp in enumerate(pe.dlgs, 1):
+            pprint("# {} = 0x{:x}".format(gidx, gidx))
+            pprint("DLGGRP 0x{:x} {}".format(grp.idx, \
+                self.fmtnum32(grp.arg1)))
+            for sidx, act in enumerate(grp.acts, 1):
+                pprint("  ON {} 0x{:x} 0x{:x} 0x{:x} # {}".format(\
+                    self.fmtop(act.opcode), act.ref, act.arg1, 
+                        act.arg2, sidx))
+                # print code
+                for didx, dlg in enumerate(act.dlgs, 1):
+                    #print(bsrec)
+                    pprint("    DLG 0x{:x} 0x{:x} # {}".format(\
+                        dlg.arg1, dlg.arg2, didx))
+                    # scan used addr
+                    usedadr = []
+                    for op in dlg.ops:
+                        if op.opcode == 0x3 or \
+                            op.opcode == 0x4: # GOTO or MENURET
+                            if op.ref not in usedadr:
+                                usedadr.append(op.ref)
+                    if len(usedadr) > 0:
+                        usedadr.append(dlg.op_start)
+                    usedmenu = {}
+                    usedcase = {}
+                    for oidx, op in enumerate(dlg.ops):
+                        cmt = ""
+                        opref = "0x{:X}".format(op.ref)
+                        opcode = self.fmtdlgop(op.opcode)
+                        if op.pos in usedadr:
+                             pprint("      label_{:X}:\n".format(
+                                op.pos))
+                        if op.opcode == 0x1: # BREAK
+                            if op.pos in usedcase:
+                                if len(usedadr) > 0:
+                                    cmt = "# end select "\
+                                        "label_{:X}, case=0x{:}"\
+                                        "".format(*usedcase[op.pos])
+                                else:
+                                    cmt = "# end "\
+                                        "select case=0x{:}".\
+                                        format(usedcase[op.pos][1])
+                        elif op.opcode == 0x2 or op.opcode == 0x8: # MENU or CIRCLE
+                            cmt = "# select "
+                            doarr = []
+                            docurr = []
+                            sellen = op.ref % 0x100
+                            skiptobrk = False
+                            menuactstart = None
+                            for oidx2, op2 in enumerate(dlg.ops[oidx + 1:]):
+                                if op2.opcode == 0x1: # BREAK
+                                    usedcase[op2.pos] = (op.pos, len(doarr))
+                                    doarr.append(docurr)
+                                    skiptobrk = False
+                                    if len(doarr) == sellen:
+                                        if op.opcode == 0x2:
+                                            menuactstart = oidx2 + oidx + 2
+                                        break
+                                    docurr = []
+                                elif op2.opcode == 0x7 and not skiptobrk: # PLAY
+                                    docurr.append("msg_{}".format(op2.ref))
+                                else:
+                                    docurr = ["complex"]
+                            if len(doarr) < sellen:
+                                cmt = "# {} select broken, "\
+                                    "required={}, got={}".\
+                                    format(opcode, sellen, len(doarr))
+                            else:
+                                cmt += ",".join(["+".join(x) for x in doarr])
+                            if menuactstart is not None:
+                                for oidx2, op2 in enumerate(dlg.ops[\
+                                        menuactstart:menuactstart + sellen]):
+                                    usedmenu[op2.pos] = (op.pos, oidx2)
+                        elif op.opcode == 0x3 or \
+                            op.opcode == 0x4: # GOTO or MENURET
+                            opref = "label_{:X}".format(op.ref)
+                            if op.pos in usedmenu:
+                                cmt = "# action menu="\
+                                    "label_{:X}, case=0x{:}".\
+                                    format(*usedmenu[op.pos])
+                        elif op.opcode == 0x7:
+                            opcode = "PLAY"
+                            if op.msg:
+                                opref = "msg_{}".format(op.ref)
+                                if op.ref < len(pe.msgs):
+                                    cmt = "# {}".format(self.escstr(
+                                        pe.msgs[op.ref].name))
+                        oparg = " 0x{:X} ".format(op.arg)
+                        if (op.opcode == 0x1 or op.opcode == 0x6) and \
+                                op.arg == 0 and op.ref == 0:
+                            oparg = ""
+                            opref = ""
+                        if cmt and verbose:
+                            pprint("        " + cmt)
+                        pprint("        {}{}{}".\
+                            format(opcode, oparg, opref))
+                    pprint("    ENDDLG # {}".format(didx))
+                pprint("  ENDON # {}".format(sidx))
+            pprint("ENDDLGGRP # {}".format(gidx))
+            pprint("")
+
+
 # check if file already exists and flag for overwrite not set
 def ckeckoverwrite(fn, args):
     if os.path.exists(fn) and not args.fo:
@@ -752,6 +879,37 @@ def action_comp(args):
     finally:
         f.close()
 
+def action_decd(args):
+    print("Decompile DIALOGUE.FIX file")
+    destpath = args.destpath
+    encoding = args.encoding
+    
+    if destpath:
+        if ckeckoverwrite(destpath, args): return -1
+        if checksame(args.sourcepath, "source", destpath, "destination"):
+            return -2
+        if not encoding:
+            encoding = "UTF-8"
+        
+    print("Input:\t{}".format(args.sourcepath))
+    print("Output:\t{}".format(destpath or "-"))
+    if destpath:
+        print("Enc:\t{}".format(encoding))
+    if args.verbose:
+        print("Flag verbose enabled")
+    
+    dcs = P12Compiler()
+    if destpath:
+        f = open(destpath, "wb")
+        try:
+            dcs.pretty_print_dlg(args.sourcepath, f, enc = encoding, \
+                verbose = args.verbose)
+        finally:
+            f.close()
+    else:
+        dcs.pretty_print_dlg(args.sourcepath, None, None, \
+                verbose = args.verbose)
+
 def internaltest(folder):
     # unpack testdata.7z
     test_arr = [
@@ -843,6 +1001,20 @@ def main():
     parser_comp.add_argument('destfolder', help = "path to output folder")
     parser_comp.set_defaults(func = action_comp)
 
+    # decompiledialog - <dialogue.fix> [[--enc <encoding>] -o <decompiled.txt>]
+    parser_decd = subparsers.add_parser("decompiledialog", aliases = ['dd'], \
+        help = "decompile dialogue.fix")
+    parser_decd.add_argument('-fo', action = 'store_true', \
+        help = "force overwrite existing output file")
+    parser_decd.add_argument("-v", '--verbose', action = 'store_true', \
+        help = "enable more verbose comments")
+    parser_decd.add_argument('-o', action = 'store', dest = "destpath",\
+        help = "output path for decompiled (default: stdout)")
+    parser_decd.add_argument('-e', "--enc", action = 'store', \
+        dest = "encoding", help = "output encoding (default: UTF-8)")
+    parser_decd.add_argument('sourcepath', help = "path to DIALOGUE.FIX file")
+    parser_decd.set_defaults(func = action_decd)
+
     # version
     parser_version = subparsers.add_parser("version", help = "program version")
     parser_version.set_defaults(func = action_version)
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 5fef532f8..286fffc92 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -469,12 +469,22 @@ class Engine:
                     r, g, b = 255, 255, 255
                 obj.cast = (r, g, b)
             
-    def load_dialogs(self):
+    def load_dialogs(self, fixname = None, lodname = None, noobjref = False):
         self.msgs = []
         # DIALOGUES.LOD
-        fp = self.curr_path + "dialogue.lod"
-        if self.fman.exists(fp):
-            f = self.fman.read_file_stream(fp)
+        
+        if lodname is None:
+            try:    
+                f = self.fman.read_file_stream(self.curr_path + "dialogue.lod")
+            except:
+                f = None
+        else:
+            try:
+                f = open(lodname, "rb")
+            except:
+                f = None
+
+        if f:
             try:
                 temp = f.read(4)
                 num_msg = struct.unpack_from("<I", temp)[0]
@@ -484,24 +494,35 @@ class Engine:
                     msg = MsgObject(len(self.msgs), \
                         wav.decode(self.enc).strip(), arg1, arg2, arg3)
                     # scan objects
-                    msg.obj = self.obj_idx.get(arg1, None)
-                    if not msg.obj:
-                        raise EngineError("DEBUG: Message ref = 0x{:x} not found".\
-                        format(obj[0]))
+                    if not noobjref:
+                        msg.obj = self.obj_idx.get(arg1, None)
+                        if not msg.obj:
+                            raise EngineError("DEBUG: Message ref = 0x{:x} not found".\
+                            format(arg1))
                     self.msgs.append(msg)
                 for i, capt in enumerate(f.read().split(b"\x00")):
                     if i < len(self.msgs):
                         self.msgs[i].name = capt.decode(self.enc)
             finally:
-                f.close()
+                if f:
+                    f.close()
 
         self.dlgs = []
         self.dlg_idx = {}
         self.dlgops = []
         # DIALOGUES.FIX
-        fp = self.curr_path + "dialogue.fix"
-        if self.fman.exists(fp):
-            f = self.fman.read_file_stream(fp)
+        if fixname is None:
+            try:    
+                f = self.fman.read_file_stream(self.curr_path + "dialogue.fix")
+            except:
+                f = None
+        else:
+            try:
+                f = open(fixname, "rb")
+            except:
+                f = None
+
+        if f:
             try:
                 temp = f.read(4)
                 num_grps = struct.unpack_from("<I", temp)[0]
@@ -519,10 +540,12 @@ class Engine:
                         opcode, ref, num_dlgs, arg1, arg2 = \
                             struct.unpack_from("<2H3I", temp)
                         act = DlgActObject(num_dlgs, opcode, ref, arg1, arg2)
-                        if ref not in self.obj_idx:
-                            raise EngineError("Dialog group 0x{:x} refered "\
-                                "to unexisted object 0x{:x}".format(grp.idx, ref))
-                        act.obj = self.obj_idx[act.ref]
+                        if not noobjref:
+                            if ref not in self.obj_idx:
+                                raise EngineError("Dialog group 0x{:x} refered "\
+                                    "to unexisted object 0x{:x}".format(
+                                    grp.idx, ref))
+                            act.obj = self.obj_idx[act.ref]
                         grp.acts.append(act)
                     for act in grp.acts:
                         act.dlgs = []
@@ -558,7 +581,8 @@ class Engine:
                 if len(oparr) > 0:
                     dlg.ops = oparr
             finally:
-                f.close()
+                if f:
+                    f.close()
                 
     def write_script(self, f):
         f.write(struct.pack("<II", len(self.objects), len(self.scenes)))


Commit: 68e2468825a57bd754f244f69bb3329d7c18f4ae
    https://github.com/scummvm/scummvm-tools/commit/68e2468825a57bd754f244f69bb3329d7c18f4ae
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Compile dialog - dialogue.lod

Changed paths:
    engines/petka/dist/setup.py
    engines/petka/p12script.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/dist/setup.py b/engines/petka/dist/setup.py
index 10d321aa4..fabaabf85 100644
--- a/engines/petka/dist/setup.py
+++ b/engines/petka/dist/setup.py
@@ -6,7 +6,8 @@ import sys, os, struct
 
 # Dependencies are automatically detected, but it might need
 # fine tuning.
-buildOptions = dict(packages = ["re", "io", "PIL", "traceback", "zlib", "gzip", "argparse", "struct", "binascii"], \
+buildOptions = dict(packages = ["re", "io", "PIL", "traceback", "zlib", "gzip",
+        "argparse", "struct", "binascii"], \
     excludes = ["_posixsubprocess"],
     include_files = ["help"],
     compressed = True, silent = True,\
diff --git a/engines/petka/p12script.py b/engines/petka/p12script.py
index a8f161889..7fc598abb 100755
--- a/engines/petka/p12script.py
+++ b/engines/petka/p12script.py
@@ -505,6 +505,211 @@ class P12Compiler:
         print("SCRIPT.DAT saved: {} objects, {} scenes".\
             format(len(pe.objects), len(pe.scenes)))
 
+
+    # =======================================================================
+    # compile DIALOGUE.FIX
+    # =======================================================================
+    def compile_dialog(self, source, destfolder, enc = None, \
+            st_fix = None, st_lod = None):
+
+        pe = petka.Engine()
+        pe.init_empty("cp1251")
+
+        mode = 0 # 0 - common, 1 - grp, 2 - act, 3 - dlg
+                 # 10 - msg (autoreset to 0)
+                 
+        # used identificators
+        self.usedid = {}
+                   
+        revOPS = {}
+        for ok, ov in petka.OPCODES.items():
+            revOPS[ov[0]] = ok
+        revDLGOPS = {}
+        for ok, ov in petka.DLGOPS.items():
+            revDLGOPS[ov[0]] = ok
+        self.reservedid = ["MSG", "DLG", "DLGGRP", "ON", "ENDDLG", \
+            "ENDDLGGRP", "ENDON"] + list(revDLGOPS.keys())
+
+        # msg array
+        compmsg = []
+        # current msg
+        compmsgitem = None
+        # grp array
+        compgrp = []
+        # current grp
+        compgrpitem = None
+        # current act
+        compactitem = None
+        # current dlg
+        compdlgitem = None
+
+        for lineno, tokens in self.tokenizer(source, enc):
+            if len(tokens) == 0:
+                continue
+            if mode == 0:
+                # accept: MSG, DLGGRP
+                cmd = tokens[0].upper()
+                if cmd == "MSG":
+                    if len(tokens) < 3 or len(tokens) > 6:
+                        raise ScriptSyntaxError("Error at {}: unknown MSG "\
+                            "syntax".format(lineno))
+                    while len(tokens) < 6:
+                        tokens.append("0")
+                    # check ident
+                    self.checkusedid(tokens[1], lineno)    
+                    self.setidentvalue(tokens[1], len(compmsg))                
+                    # check wavfile name (1..12)
+                    if len(tokens[2]) < 1 or len(tokens[2]) > 12:
+                        raise ScriptSyntaxError("Error at {}: bad filename "\
+                            "in MSG \"{}\"".format(lineno, tokens[2]))
+                    mode = 10
+                    compmsgitem = {"ident": tokens[1], "wav": tokens[2], \
+                        "args": tokens[3:], "lineno": lineno}
+                    compmsg.append(compmsgitem)
+                elif cmd == "DLGGRP":
+                    # dlggrp
+                    mode = 1
+                    # check syntax
+                    if len(tokens) < 2 or len(tokens) > 3:
+                        raise ScriptSyntaxError("Error at {}: unknown DLGGRP "\
+                            "syntax".format(lineno))
+                    while len(tokens) < 3:
+                        tokens.append("0")
+                    compgrpitem = {"grp_id": tokens[1], "arg": tokens[2], \
+                        "acts": [], "lineno": lineno}
+                else:
+                    raise ScriptSyntaxError("Error at {}: unknown syntax "\
+                        "\"{}\"".format(lineno, cmd))
+            elif mode == 10:
+                # MSG, 2nd string, one token - msg for subtitle
+                if len(tokens) != 1:
+                    raise ScriptSyntaxError("Error at {}: MSG syntax error, "\
+                        "message required".format(lineno))
+                compmsgitem["msg"] = tokens[0]
+                compmsgitem = None
+                mode = 0
+            elif mode == 1:
+                # accept: ON, ENDDLGGRP
+                cmd = tokens[0].upper()
+                if cmd == "ON":
+                    # on
+                    mode = 2
+                    # check syntax
+                    if len(tokens) < 3 or len(tokens) > 5:
+                        raise ScriptSyntaxError("Error at {}: unknown ON "\
+                            "syntax".format(lineno))
+                    while len(tokens) < 5:
+                        tokens.append("0")
+                    if tokens[1] in revOPS:
+                        # opcode
+                        don = revOPS[tokens[1]]
+                    else:
+                        don = self.convertnum(tokens[1])
+                        if don is None:
+                            raise ScriptSyntaxError("Error at {}: unknown ON "\
+                                "OPREF ""\"{}\" in ON".\
+                                format(lineno, tokens[1]))
+                        don = self.check16(don, "ON opref", lineno)
+                    compactitem = {"don": don, "donref": tokens[2], 
+                        "args": tokens[3:], "dlgs": [], "lineno": lineno}
+                elif cmd == "ENDDLGGRP" and len(tokens) == 1:
+                    mode = 0
+                    compgrp.append(compgrpitem)
+                    compgrpitem = None
+                else:
+                    raise ScriptSyntaxError("Error at {}: unknown DLGGRP "\
+                        "syntax \"{}\"".format(lineno, cmd))
+            elif mode == 2:
+                # accept: DLG, ENDON
+                cmd = tokens[0].upper()
+                if cmd == "DLG":
+                    # dlg
+                    mode = 3
+                    # check syntax
+                    if len(tokens) < 1 or len(tokens) > 3:
+                        raise ScriptSyntaxError("Error at {}: unknown DLG "\
+                            "syntax".format(lineno))
+                    while len(tokens) < 3:
+                        tokens.append("0")
+                    compdlgitem = {"args": tokens[1:], "dlgops": [], \
+                        "lineno": lineno}
+                elif cmd == "ENDON" and len(tokens) == 1:
+                    mode = 1
+                    compgrpitem["acts"].append(compactitem)
+                    compactitem = None
+                else:
+                    raise ScriptSyntaxError("Error at {}: unknown ON "\
+                        "syntax \"{}\"".format(lineno, cmd))
+            elif mode == 3:
+                # dlgopcode or ENDDLG
+                cmd = tokens[0].upper()
+                if cmd == "ENDDLG" and len(tokens) == 1:
+                    compactitem["dlgs"].append(compdlgitem)
+                    compdlgitem = None
+                    mode = 2
+                elif cmd[-1:] == ":":
+                    # label case
+                    label = cmd[:-1]
+                    self.checkusedid(cmd[:-1], lineno)
+                    self.setidentvalue(cmd[:-1], len(compdlgitem["dlgops"]))
+                else:
+                    # check format
+                    if len(tokens) < 1 or len(tokens) > 3:
+                        raise ScriptSyntaxError("Error at {}: unknown DLGOP "\
+                            "syntax in DLG".format(lineno))
+                    while len(tokens) < 3:
+                        tokens.append("0")
+                    op = {}
+                    if cmd in revDLGOPS:
+                        # opcode
+                        opcode = revDLGOPS[cmd]
+                    else:
+                        opcode = self.convertnum(cmd)
+                        if opcode is None:
+                            raise ScriptSyntaxError("Error at {}: unknown "\
+                                "DLGOP \"{}\" in DLG".\
+                                format(lineno, cmd))
+                        opcode = self.check8(opcode, "dlgopcode", lineno)
+                    compdlgitem["dlgops"].append({"opcode": opcode, \
+                        "lineno": lineno, \
+                        "msg_ref": tokens[2], "arg": tokens[1]})
+            else:
+                raise ScriptSyntaxError("Error at {}: unknown parser mode {}".\
+                    format(lineno, mode))
+            
+        # check unclosed objects
+        if mode != 0:
+            raise ScriptSyntaxError("Error at {}: unfinished structure".\
+                format(lineno))
+
+        # second stage - MSG
+        for msg in compmsg:
+            # msg arguments
+            fmt = []
+            for i in range(3):
+                fmt.append(("MSG argument {}".format(i + 1), \
+                    self.check32, True))
+            argnum = self.convertargs(fmt, msg["args"], msg["lineno"])
+            # build MSGREC
+            msgrec = petka.engine.MsgObject(
+                len(pe.msgs), msg["wav"], argnum[0], argnum[1], argnum[2])
+            msgrec.name = msg["msg"]
+            pe.msgs.append(msgrec)
+
+        if destfolder is not None:
+            f = open(os.path.join(destfolder, "dialogue.lod"), "wb")
+        else:
+            f = st_lod
+        try:
+            pe.write_lod(f)
+        finally:
+            if destfolder is not None:
+                f.close()
+        print("DALOGUE.LOD saved: {} messages".\
+            format(len(pe.msgs)))
+
+
+
     # =======================================================================
     # decompile utils
     # =======================================================================
@@ -699,7 +904,7 @@ class P12Compiler:
         for msg in pe.msgs:
             pprint("# {} = 0x{:x}".format(msg.idx, msg.idx))
             pprint("MSG msg_{} \"{}\" 0x{:x} 0x{:x} 0x{:x}".format(\
-                msg.idx, msg.wav, msg.arg1, msg.arg2, msg.arg3))
+                msg.idx, msg.msg_wav, msg.msg_arg1, msg.msg_arg2, msg.msg_arg3))
             pprint(" \"{}\"".format(self.escstr(msg.name)))
             pprint("")
     
@@ -910,8 +1115,35 @@ def action_decd(args):
         dcs.pretty_print_dlg(args.sourcepath, None, None, \
                 verbose = args.verbose)
 
+def action_compd(args):
+    print("Compile DIALOGUE.FIX file")
+    print("Input:\t{}".format(args.sourcepath))
+    print("Output:\t{}".format(args.destfolder))
+    print("Enc:\t{}".format(args.encoding or "UTF-8"))
+
+    dcs = P12Compiler()
+    if os.path.exists(args.destfolder) and not args.fo:
+        cnt = 0
+        lst = ["dialogue.fix", "dialogue.lod"]
+        for item in lst:
+            if find_in_folder(args.destfolder, item, False):
+                print("Error: destination file \"{}\" already "\
+                    "exists, use -fo to overwrite".format(item))
+                return
+
+    if not os.path.exists(args.destfolder):
+        os.makedirs(args.destfolder)
+    f = open(args.sourcepath, "rb")
+    try:
+        dcs.compile_dialog(f, args.destfolder, args.encoding)
+    except ScriptSyntaxError as e:
+        if args.trace_error:
+            traceback.print_exc()
+        print(e, file = sys.stderr)
+    finally:
+        f.close()
+
 def internaltest(folder):
-    # unpack testdata.7z
     test_arr = [
       "p1demo",
       "p1-0", "p1-1", "p1-2", "p1-3",
@@ -953,6 +1185,31 @@ def internaltest(folder):
             print("BACKGRND.BG - mismatch")
             break
 
+        # dialogue
+        dcs = P12Compiler()
+        path = find_in_folder(testbase, "dialogue.fix")
+        if not os.path.exists(path):
+            print("All ok - no dialogue")
+            continue
+            
+        mems = io.BytesIO()        
+        dcs.pretty_print_dlg(path, mems)
+        print("Decompiled dialogue:", mems.tell())
+        # compile back
+        memfix = io.BytesIO()
+        memlod = io.BytesIO()
+        mems.seek(0)
+        ndcs = P12Compiler()
+        ndcs.compile_dialog(mems, None, None, \
+            memfix, memlod)
+        # compare
+        if not compare(path, memfix):
+            print("DIALOGUE.FIX - mismatch")
+            break
+        if not compare(dcs.find_in_folder(testbase, "dialogue.lod"), memlod):
+            print("DIALOGUE.LOD - mismatch")
+            break
+
         print("All ok")
 
 def action_version(args):
@@ -1015,6 +1272,19 @@ def main():
     parser_decd.add_argument('sourcepath', help = "path to DIALOGUE.FIX file")
     parser_decd.set_defaults(func = action_decd)
 
+    # compiledialog - <source.txt> <destination folder> [--enc <encoding>]
+    parser_compd = subparsers.add_parser("compiledialog", aliases = ['cd'], \
+        help = "compile dialogue.fix")
+    parser_compd.add_argument('-fo', action = 'store_true', \
+        help = "force overwrite existing output files")
+    parser_compd.add_argument('-e', "--enc", action = 'store', \
+        dest = "encoding", help = "output encoding (default: UTF-8)")
+    parser_compd.add_argument('-te', "--trace-error", action = 'store_true', \
+        help = "trace syntax error")
+    parser_compd.add_argument('sourcepath', help = "path to SOURCE.TXT file")
+    parser_compd.add_argument('destfolder', help = "path to output folder")
+    parser_compd.set_defaults(func = action_compd)
+
     # version
     parser_version = subparsers.add_parser("version", help = "program version")
     parser_version.set_defaults(func = action_version)
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 286fffc92..0f765c207 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -105,10 +105,10 @@ class ScrOpObject:
 class MsgObject:
     def __init__(self, idx, wav, arg1, arg2, arg3):
         self.idx = idx
-        self.wav = wav   # wav filename
-        self.arg1 = arg1 # reference to object
-        self.arg2 = arg2
-        self.arg3 = arg3
+        self.msg_wav = wav   # wav filename
+        self.msg_arg1 = arg1 # reference to object
+        self.msg_arg2 = arg2
+        self.msg_arg3 = arg3
         self.name = None
 
 class DlgGrpObject:
@@ -164,7 +164,11 @@ class Engine:
         self.scenes = []
         self.obj_idx = {}
         self.scn_idx = {}
-        
+        self.msgs = []
+        self.dlgs = []
+        self.dlg_idx = {}
+        self.dlgops = []
+                
     def parse_ini(self, f):
         # parse ini settings
         curr_sect = None
@@ -612,3 +616,15 @@ class Engine:
                     ref[3], ref[4], ref[5]))
                 
 
+    def write_lod(self, f):
+        f.write(struct.pack("<I", len(self.msgs)))
+        for msg in self.msgs:
+            wav = msg.msg_wav.encode(self.enc)
+            while len(wav) < 12:
+                wav += b"\0"
+            f.write(struct.pack("<I12sII", msg.msg_arg1, wav, msg.msg_arg2, 
+                msg.msg_arg3))
+        for msg in self.msgs:
+            txt = msg.name.encode(self.enc)
+            f.write(txt + b"\0")
+


Commit: 1f1901479cbf5b66caaf4655e0adc8d0bae0879a
    https://github.com/scummvm/scummvm-tools/commit/1f1901479cbf5b66caaf4655e0adc8d0bae0879a
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Compiler for dialogs

Changed paths:
  A engines/petka/help/comp_dlg.txt
  A engines/petka/help/comp_scr.txt
  A engines/petka/help/compiler.txt
    engines/petka/help/changes.txt
    engines/petka/help/index.txt
    engines/petka/help/list
    engines/petka/p12explore.py
    engines/petka/p12script.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 81d630548..cab09d1e3 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,6 +1,13 @@
 Что нового
 ==========
 
+2014-06-03 версия 0.3
+---------------------
+Добавлен консольный компилятор и декомпилятор для файлов SCRIPT.DAT, 
+  BACKGRND.BG, DIALOGUE.FIX, DIALOGUE.FIX
+Выполнен рефакторинг кода
+Обновлена справка
+
 2014-05-23 версия 0.2o
 ----------------------
 Исправление ошибки при открытии ресурса.
diff --git a/engines/petka/help/comp_dlg.txt b/engines/petka/help/comp_dlg.txt
new file mode 100644
index 000000000..d9d334bb5
--- /dev/null
+++ b/engines/petka/help/comp_dlg.txt
@@ -0,0 +1,84 @@
+Синтаксис компиляции DIALOGUE.FIX
+
+romiq.kh at gmail.com
+v0.3 2014-06-03
+
+<i>Синтаксис файла</i>
+
+Команды указываеются построчно, разбиение одной команды на несколько строк.
+Часть строки после символа '#' считается комментарием.
+
+Обозначения:
+ * \<обязательные параметры\>
+ * [необязательные]
+ * "строка"
+
+В файле могут быть определены следующие записи:
+
+ * MSG - сообщения, попадают в DIALOGUE.LOD
+ * DLGGRP - группы сообщений, попадают в DIALOGUE.FIX
+ 
+<i>MSG</i>
+
+Каждая запись должна состоять из 2х строк. В второй строке должна быть только 
+одна запись.
+
+MSG \<идентификатор объекта\> \<"имя файла wav"\> [\<аргумент 1\> [\<аргумент 2\> [\<аргумент 3\>]]]
+  \<"Текст сообщения для субтитров"\>
+
+Аргументы - число от 0 до 0xffffffff. Аргументы могут отутствовать, в этом 
+случае они будут заменены 0.
+
+<i>DLGGRP</i>
+
+DLGGRP \<номер группы\> [\<аргумент\>]
+  [обработчики - ON]
+ENDDLGGRP
+
+Аргумент - число от 0 до 0xffffffff. Аргумент может отутствовать, в этом 
+случае он будет заменён 0.
+
+<i>ON</i>
+
+ON \<код операции\> \<идентификатор объекта\> [\<аргумент 1\> [\<аргумент 2\>]]
+  [список - DLG]
+ENDON
+
+Код операции - код операции (см. <a href="/help/comp_scr">Синтаксис компиляции SCRIPT.DAT</a>).
+
+Идентификатор объекта - идентификатор объекта к которому была применена 
+операция.
+
+Аргументы - числа от 0 до 0xffffffff. Аргументы могут отутствовать, в этом 
+случае они будут заменены 0.
+
+<i>DLG</i>
+
+DLGSET [\<аргумент 1\> [\<аргумент 2\>]]
+  [операции]
+  [\<метка перехода\>:]
+ENDDLGSET
+
+Аргументы - число от 0 до 0xffffffff. Аргументы могут отутствовать, в этом 
+случае они будут заменены 0.
+
+<i>операции</i>
+
+\<код операции\> [\<аргумент\> [\<ссылка на сообщение\>]]
+
+Аргумент - число от 0 до 0xff. Аргумент может отутствовать, в этом 
+случае он будет заменён 0.
+
+Отсутствующая ссылка также будет заменена на 0.
+
+Операции:
+
+ * BREAK
+ * MENU
+ * GOTO
+ * MENURET
+ * RETURN
+ * PLAY
+ * CIRCLE
+ * число 0..0xff
+
diff --git a/engines/petka/help/comp_scr.txt b/engines/petka/help/comp_scr.txt
new file mode 100644
index 000000000..ccf54f183
--- /dev/null
+++ b/engines/petka/help/comp_scr.txt
@@ -0,0 +1,131 @@
+Синтаксис компиляции SCRIPT.DAT
+
+romiq.kh at gmail.com
+v0.3 2014-06-03
+
+<i>Синтаксис файла</i>
+
+Команды указываеются построчно, разбиение одной команды на несколько строк.
+Часть строки после символа '#' считается комментарием.
+
+Обозначения:
+ * \<обязательные параметры\>
+ * [необязательные]
+ * "строка"
+
+В файле могут быть определены следующие записи:
+
+ * OBJ - объекты, попадают в первую половину SCRIPT.DAT
+ * SCENE - сцены, попадают во вторую половину SCRIPT.DAT
+ * RES - ресурсы, попадают в RESOURCE.QRC
+ 
+<i>OBJ</i>
+
+OBJ \<идентификатор объекта\> \<номер 0..0xffff\> "\<Имя\>"
+  [обработчики событий - ON]
+ENDOBJ
+
+<i>SCENE</i>
+
+SCENE \<идентификатор сцены\> \<номер 0..0xffff\> "\<Имя\>"
+  [обработчики событий - ON]
+  [ссылки на другие объекты - REF]
+ENDSCENE
+
+<i>RES</i>
+
+RES \<идентификатор ресурса\> \<номер 0..0xffff\> "\<путь\>"
+
+<i>ON</i>
+
+Обработчик события. Вызывается при применении на объекте операции.
+При загрузке скрипта ко всем объектам и сценам применяется операция TOTALINIT.
+
+ON \<операция\>
+ON \<операция\> [\<статус 0..0xff\> \<идентификатор объекта 0..0xffff\>]
+ON \<операция\> [\<статус 0..0xff\> THIS]
+  [операции]
+ENDON
+
+<i>REF</i>
+
+REF \<ссылка на объект\> [\<аргумент 1\> [\<аргумент 2\> [\<аргумент 3\> [\<аргумент 4\> [\<аргумент 5\>]]]]]
+
+Отсутствующие аргументы будут заменены на -1 (0xffffffff).
+ 
+<i>ZEROREF</i>
+
+Используется если запись в backgrnd.bg необходима, но ни один из объектов не 
+добавлен к сцене. Если у сцены уже были указаны REF использвание этого операнда 
+невозможно.
+
+<i>операции</i>
+
+\<код операции\> \<ссылка на объект\> [\<аргумент 1\> [\<аргумент 2\> [\<аргумент 3\>]]]
+
+Ссылка на объект может быть указана на текущий объекта с помощью слова THIS.
+
+Отсутствующие аргументы будут заменены на -1 (0xffff).
+
+Операции:
+
+ * USE
+ * SETPOS
+ * GOTO
+ * LOOK
+ * SAY
+ * TAKE
+ * WALK
+ * TALK
+ * END
+ * SET
+ * SHOW
+ * HIDE
+ * DIALOG
+ * ZBUFFER
+ * TOTALINIT
+ * ANIMATE
+ * STATUS
+ * ADDINV
+ * DELINV
+ * STOP
+ * CURSOR
+ * OBJECTUSE
+ * ACTIVE
+ * SAID
+ * SETSEQ
+ * ENDSEQ
+ * CHECK
+ * IF
+ * DESCRIPTION
+ * HALF
+ * WALKTO
+ * WALKVICH
+ * INITBG
+ * USERMSG
+ * SYSTEM
+ * SETZBUFFER
+ * CONTINUE
+ * MAP
+ * PASSIVE
+ * NOMAP
+ * SETINV
+ * BGSFX
+ * MUSIC
+ * IMAGE
+ * STAND
+ * ON
+ * OFF
+ * PLAY
+ * LEAVEBG
+ * SHAKE
+ * SP
+ * RANDOM
+ * JUMP
+ * JUMPVICH
+ * PART
+ * CHAPTER
+ * AVI
+ * TOMAP
+ * число 0..0xffff
+
diff --git a/engines/petka/help/compiler.txt b/engines/petka/help/compiler.txt
new file mode 100644
index 000000000..25e0bce49
--- /dev/null
+++ b/engines/petka/help/compiler.txt
@@ -0,0 +1,92 @@
+Консольный компилятор и декомпилятор
+
+Команды
+-------
+
+ * Декомпиляция SCRIPT.DAT
+ * Компиляция SCRIPT.DAT
+ * Декомпиляция DIALOGUE.FIX
+ * Компиляция DIALOGUE.FIX
+
+Декомпиляция SCRIPT.DAT
+-----------------------
+
+Вывод в терминал
+
+  p12script decompile PART_0/SCRIPT.DAT
+  
+Вывод в файл
+
+  p12script decompile PART_0/SCRIPT.DAT -o decomp0.txt
+  
+Вывод в файл c другой кодировкой
+
+  p12script decompile PART_0/SCRIPT.DAT -o decomp0.txt -e cp1251
+  
+Для того чтобы перезаписать существующий файл можно использовать ключ '-fo'.
+
+Для полного восстановления необходимо чтобы файлы BACKGRND.DAT и RESOURCE.QRC
+были в том же каталоге что и SCRIPT.DAT.
+
+Ключ --decompile-sorted позволяет отображать объекты рядом с сценами, которые 
+их используют. ВАЖНО: такой порядок может привести к другому порядку их 
+нахождения в файле. В этом случае компилированный файл может не совпасть с 
+оригиналом из которого сделана декомпиляция.
+
+Компиляция SCRIPT.DAT
+---------------------
+
+Компиляция файла в каталог
+
+  p12script compile source.txt folder
+  
+Компиляция файла c другой кодировкой в каталог
+  
+  p12script compile source.txt folder -e cp1251
+
+Компилятор не будет записывать файлы в каталог где уже есть файлы SCRIPT.DAT, 
+BACKGRND.DAT или RESOURCE.QRC.
+Для того чтобы обойти это можно использовать ключ '-fo'. 
+
+Синтаксис файла приведён в документе <a href="/help/comp_scr">Синтаксис компиляции SCRIPT.DAT</a>.
+ 
+Декомпиляция DIALOGUE.FIX
+-------------------------
+
+Вывод в терминал
+
+  p12script decompiledialog PART_1/DIALOGUE.FIX
+  
+Вывод в файл
+
+  p12script decompiledialog PART_1/DIALOGUE.FIX -o decomp0.txt
+  
+Вывод в файл c другой кодировкой
+
+  p12script decompiledialog PART_1/DIALOGUE.FIX -o decomp0.txt -e cp1251
+  
+Для того чтобы перезаписать существующий файл можно использовать ключ '-fo'.
+
+Для декомпиляции необходимо чтобы файл DIALOGUE.LOD был в том же каталоге что 
+и DIALOGUE.FIX.
+
+Ключ --verbose позволяет оценить на какое сообщение ссылается элемент диалога.
+
+Компиляция DIALOGUE.FIX
+-----------------------
+
+Компиляция файла в каталог
+
+  p12script compiledialog source.txt folder
+  
+Компиляция файла c другой кодировкой в каталог
+  
+  p12script compiledialog source.txt folder -e cp1251
+
+Компилятор не будет записывать файлы в каталог где уже есть файлы DIALOGUE.FIX
+или DIALOGUE.LOD.
+Для того чтобы обойти это можно использовать ключ '-fo'. 
+
+Синтаксис файла приведён в документе <a href="/help/comp_dlg">Синтаксис компиляции DIALOGUE.FIX</a>.
+
+
diff --git a/engines/petka/help/index.txt b/engines/petka/help/index.txt
index ee2fe76ec..1b5b8f78c 100644
--- a/engines/petka/help/index.txt
+++ b/engines/petka/help/index.txt
@@ -1,6 +1,6 @@
 Справка
 
-Версия: 0.2o 2014-05-20
+Версия: 0.3 2014-06-03
 
  * <a href="/help/changes">Что нового</a>
  * <a href="/help/faq">Часто задаваемые вопросы</a>
@@ -26,3 +26,7 @@
  * <a href="/help/info">Справочники</a>
  * <a href="/help/about">О программе</a>
 
+Утилиты
+
+ * <a href="/help/compiler">Консольный компилятор и декомпилятор</a>
+
diff --git a/engines/petka/help/list b/engines/petka/help/list
index dbab18eeb..0bc2a2ba0 100644
--- a/engines/petka/help/list
+++ b/engines/petka/help/list
@@ -16,4 +16,7 @@ cmdline
 support
 info
 about
+compiler
+comp_scr
+comp_dlg
 
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 603659665..ce51ef530 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1469,11 +1469,11 @@ class App(tkinter.Frame):
         else:
             # msg info
             self.add_info("<b>Message</b>: {}\n".format(path[1]))
-            self.add_info("  wav:    {}\n".format(msg.wav))
+            self.add_info("  wav:    {}\n".format(msg.msg_wav))
             self.add_info("  object: " + self.fmt_hl_obj(msg.obj.idx, True) + 
                 "\n")
-            self.add_info("  arg2:   {} (0x{:X})\n".format(msg.arg2, msg.arg2))
-            self.add_info("  arg3:   {} (0x{:X})\n".format(msg.arg3, msg.arg3))
+            self.add_info("  arg2:   {a} (0x{a:X})\n".format(a = msg.msg_arg2))
+            self.add_info("  arg3:   {a} (0x{a:X})\n".format(a = msg.msg_arg3))
             self.add_info("\n{}\n".format(hlesc(msg.name)))
             if self.tran:
                 self.add_info("\n<i>Translated:</i>\n{}\n".\
@@ -1520,7 +1520,7 @@ class App(tkinter.Frame):
             # grp info
             self.add_info("<b>Dialog group</b>: {} (0x{:X})\n".format(\
                 grp.idx, grp.idx))
-            self.add_info("  arg1: {} (0x{:X})\n\n".format(grp.arg1, grp.arg1))
+            self.add_info("  arg1: {a} (0x{a:X})\n\n".format(a = grp.grp_arg1))
             self.add_info("<b>Dialog handlers<b>: {}\n".format(len(grp.acts)))
             for idx, act in enumerate(grp.acts):
                 self.add_info("  {}) <u>on {} {} 0x{:X} 0x{:X}</u>, dlgs: "\
diff --git a/engines/petka/p12script.py b/engines/petka/p12script.py
index 7fc598abb..6ccdce86e 100755
--- a/engines/petka/p12script.py
+++ b/engines/petka/p12script.py
@@ -420,7 +420,7 @@ class P12Compiler:
                         act["lineno"])
                 else:
                     # get by ident
-                    self.checkident(act["sonref"], "ON object ref", \
+                    self.checkident(act["sonref"], "ON object ref",
                         act["lineno"])
                     sonref = self.getidentvalue(act["sonref"])
                 onrec = petka.engine.ScrActObject(act["son"], 
@@ -437,13 +437,13 @@ class P12Compiler:
                         opref = self.check16(opref, "OP object", op["lineno"])
                     else:
                         # get by ident
-                        self.checkident(op["obj_ref"], "OP object", \
+                        self.checkident(op["obj_ref"], "OP object",
                             op["lineno"])
                         opref = self.getidentvalue(op["obj_ref"])
                     # arguments
                     fmt = []
                     for i in range(3):
-                        fmt.append(("OP argument {}".format(i + 1), \
+                        fmt.append(("OP argument {}".format(i + 1),
                             self.check16, True))
                     argnum = self.convertargs(fmt, op["args"], op["lineno"])
                     oprec =  petka.engine.ScrOpObject(opref, op["opcode"], 
@@ -509,7 +509,7 @@ class P12Compiler:
     # =======================================================================
     # compile DIALOGUE.FIX
     # =======================================================================
-    def compile_dialog(self, source, destfolder, enc = None, \
+    def compile_dialog(self, source, destfolder, enc = None,
             st_fix = None, st_lod = None):
 
         pe = petka.Engine()
@@ -527,7 +527,7 @@ class P12Compiler:
         revDLGOPS = {}
         for ok, ov in petka.DLGOPS.items():
             revDLGOPS[ov[0]] = ok
-        self.reservedid = ["MSG", "DLG", "DLGGRP", "ON", "ENDDLG", \
+        self.reservedid = ["MSG", "DLG", "DLGGRP", "ON", "ENDDLG",
             "ENDDLGGRP", "ENDON"] + list(revDLGOPS.keys())
 
         # msg array
@@ -542,6 +542,8 @@ class P12Compiler:
         compactitem = None
         # current dlg
         compdlgitem = None
+        # dlgop count
+        compdlgops = 0
 
         for lineno, tokens in self.tokenizer(source, enc):
             if len(tokens) == 0:
@@ -563,7 +565,7 @@ class P12Compiler:
                         raise ScriptSyntaxError("Error at {}: bad filename "\
                             "in MSG \"{}\"".format(lineno, tokens[2]))
                     mode = 10
-                    compmsgitem = {"ident": tokens[1], "wav": tokens[2], \
+                    compmsgitem = {"ident": tokens[1], "wav": tokens[2],
                         "args": tokens[3:], "lineno": lineno}
                     compmsg.append(compmsgitem)
                 elif cmd == "DLGGRP":
@@ -575,7 +577,7 @@ class P12Compiler:
                             "syntax".format(lineno))
                     while len(tokens) < 3:
                         tokens.append("0")
-                    compgrpitem = {"grp_id": tokens[1], "arg": tokens[2], \
+                    compgrpitem = {"grp_id": tokens[1], "arg": tokens[2],
                         "acts": [], "lineno": lineno}
                 else:
                     raise ScriptSyntaxError("Error at {}: unknown syntax "\
@@ -631,7 +633,7 @@ class P12Compiler:
                             "syntax".format(lineno))
                     while len(tokens) < 3:
                         tokens.append("0")
-                    compdlgitem = {"args": tokens[1:], "dlgops": [], \
+                    compdlgitem = {"args": tokens[1:], "dlgops": [],
                         "lineno": lineno}
                 elif cmd == "ENDON" and len(tokens) == 1:
                     mode = 1
@@ -647,11 +649,11 @@ class P12Compiler:
                     compactitem["dlgs"].append(compdlgitem)
                     compdlgitem = None
                     mode = 2
-                elif cmd[-1:] == ":":
+                elif len(tokens) == 1 and cmd[-1:] == ":":
                     # label case
                     label = cmd[:-1]
-                    self.checkusedid(cmd[:-1], lineno)
-                    self.setidentvalue(cmd[:-1], len(compdlgitem["dlgops"]))
+                    self.checkusedid(tokens[0][:-1], lineno)
+                    self.setidentvalue(tokens[0][:-1], compdlgops)
                 else:
                     # check format
                     if len(tokens) < 1 or len(tokens) > 3:
@@ -670,7 +672,8 @@ class P12Compiler:
                                 "DLGOP \"{}\" in DLG".\
                                 format(lineno, cmd))
                         opcode = self.check8(opcode, "dlgopcode", lineno)
-                    compdlgitem["dlgops"].append({"opcode": opcode, \
+                    compdlgops += 1
+                    compdlgitem["dlgops"].append({"opcode": opcode,
                         "lineno": lineno, \
                         "msg_ref": tokens[2], "arg": tokens[1]})
             else:
@@ -708,6 +711,72 @@ class P12Compiler:
         print("DALOGUE.LOD saved: {} messages".\
             format(len(pe.msgs)))
 
+        for grp in compgrp:
+            fmt = [("DLGGRP number", self.check32, False),\
+                ("DLGGRP argument", self.check32, True)]
+            argnum = self.convertargs(fmt, [grp["grp_id"], grp["arg"]], \
+                grp["lineno"])
+            # build DLGGRP
+            grprec = petka.engine.DlgGrpObject(argnum[0], argnum[1])
+            grprec.acts = []
+            for act in grp["acts"]:
+                # act objref
+                if act["donref"].upper() == "THIS":
+                    donref = grprec.grp_id
+                else:
+                    donref = self.convertnum(act["donref"])
+                if donref is not None:
+                    # direct number
+                    donref = self.check16(donref, "ON object ref", 
+                        act["lineno"])
+                else:
+                    # get by ident
+                    self.checkident(act["donref"], "ON object ref",
+                        act["lineno"])
+                    donref = self.getidentvalue(act["donref"])
+                # act arguments
+                fmt = []
+                for i in range(2):
+                    fmt.append(("ON argument {}".format(i + 1),
+                        self.check32, True))
+                argnum = self.convertargs(fmt, act["args"], act["lineno"])
+                # build ON
+                actrec = petka.engine.DlgActObject(act["don"], donref,
+                    argnum[0], argnum[1])
+                actrec.dlgs = []
+                for dlg in act["dlgs"]:
+                    # dlg arguments
+                    fmt = []
+                    for i in range(2):
+                        fmt.append(("DLG argument {}".format(i + 1),
+                            self.check32, True))
+                    argnum = self.convertargs(fmt, dlg["args"], dlg["lineno"])
+                    # build DLG
+                    dlgrec = petka.engine.DlgObject(len(pe.dlgops), 
+                        argnum[0], argnum[1])
+                    for op in dlg["dlgops"]:
+                        fmt = [("DLGOP argument", self.check8, False),
+                            ("DLGOP ref", self.check16, True)]
+                        argnum = self.convertargs(fmt, [op["arg"],
+                            op["msg_ref"]], op["lineno"])
+                        oprec = petka.engine.DlgOpObject(op["opcode"],
+                            argnum[0], argnum[1])
+                        pe.dlgops.append(oprec)
+                    actrec.dlgs.append(dlgrec)
+                grprec.acts.append(actrec)
+            pe.dlgs.append(grprec)
+
+        if destfolder is not None:
+            f = open(os.path.join(destfolder, "dialogue.fix"), "wb")
+        else:
+            f = st_fix
+        try:
+            pe.write_fix(f)
+        finally:
+            if destfolder is not None:
+                f.close()
+        print("DALOGUE.FIX saved: {} groups, {} dialog opcodes".\
+            format(len(pe.dlgs), len(pe.dlgops)))
 
 
     # =======================================================================
@@ -911,7 +980,7 @@ class P12Compiler:
         for gidx, grp in enumerate(pe.dlgs, 1):
             pprint("# {} = 0x{:x}".format(gidx, gidx))
             pprint("DLGGRP 0x{:x} {}".format(grp.idx, \
-                self.fmtnum32(grp.arg1)))
+                self.fmtnum32(grp.grp_arg1)))
             for sidx, act in enumerate(grp.acts, 1):
                 pprint("  ON {} 0x{:x} 0x{:x} 0x{:x} # {}".format(\
                     self.fmtop(act.opcode), act.ref, act.arg1, 
@@ -937,7 +1006,7 @@ class P12Compiler:
                         opref = "0x{:X}".format(op.ref)
                         opcode = self.fmtdlgop(op.opcode)
                         if op.pos in usedadr:
-                             pprint("      label_{:X}:\n".format(
+                             pprint("      label_{:X}:".format(
                                 op.pos))
                         if op.opcode == 0x1: # BREAK
                             if op.pos in usedcase:
@@ -1206,7 +1275,7 @@ def internaltest(folder):
         if not compare(path, memfix):
             print("DIALOGUE.FIX - mismatch")
             break
-        if not compare(dcs.find_in_folder(testbase, "dialogue.lod"), memlod):
+        if not compare(find_in_folder(testbase, "dialogue.lod"), memlod):
             print("DIALOGUE.LOD - mismatch")
             break
 
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 0f765c207..1b6715cbd 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -112,15 +112,13 @@ class MsgObject:
         self.name = None
 
 class DlgGrpObject:
-    def __init__(self, idx, num_acts, arg1):
+    def __init__(self, idx, arg1):
         self.idx = idx
-        self.num_acts = num_acts # store array length while loading
-        self.arg1 = arg1
+        self.grp_arg1 = arg1
         self.acts = None # dialog handlers
 
 class DlgActObject:
-    def __init__(self, num_dlgs, opcode, ref, arg1, arg2):
-        self.num_dlgs = num_dlgs # store array length while loading
+    def __init__(self, opcode, ref, arg1, arg2):
         self.opcode = opcode # handler: opcode filter
         self.ref = ref       # handler: object idx filter
         self.arg1 = arg1
@@ -136,12 +134,11 @@ class DlgObject:
         self.ops = None          # operations list
 
 class DlgOpObject:
-    def __init__(self, opcode, arg, ref, pos):
+    def __init__(self, opcode, arg, ref):
         self.opcode = opcode    # dialog opcode
         self.arg = arg          # argument (ref, offset etc.)
         self.ref = ref          # message idx
         self.msg = None         # message
-        self.pos = pos          # position in opcodes list
         
 class Engine:
     def __init__(self):
@@ -533,7 +530,8 @@ class Engine:
                 for i in range(num_grps):
                     temp = f.read(12)
                     idx, num_acts, arg1 = struct.unpack_from("<III", temp)
-                    grp = DlgGrpObject(idx, num_acts, arg1)
+                    grp = DlgGrpObject(idx, arg1)
+                    grp.num_acts = num_acts
                     self.dlgs.append(grp)
                 opref = {}
                 for grp in self.dlgs:
@@ -543,7 +541,8 @@ class Engine:
                         temp = f.read(16)
                         opcode, ref, num_dlgs, arg1, arg2 = \
                             struct.unpack_from("<2H3I", temp)
-                        act = DlgActObject(num_dlgs, opcode, ref, arg1, arg2)
+                        act = DlgActObject(opcode, ref, arg1, arg2)
+                        act.num_dlgs = num_dlgs
                         if not noobjref:
                             if ref not in self.obj_idx:
                                 raise EngineError("Dialog group 0x{:x} refered "\
@@ -569,7 +568,8 @@ class Engine:
                 for oidx, i in enumerate(range(num_ops)):
                     temp = f.read(4)
                     ref, arg, code  = struct.unpack_from("<HBB", temp)
-                    dlgop = DlgOpObject(code, arg, ref, oidx)
+                    dlgop = DlgOpObject(code, arg, ref)
+                    dlgop.pos = oidx
                     if ref < len(self.msgs):
                         dlgop.msg = self.msgs[ref]
                     self.dlgops.append(dlgop)
@@ -628,3 +628,19 @@ class Engine:
             txt = msg.name.encode(self.enc)
             f.write(txt + b"\0")
 
+    def write_fix(self, f):
+        f.write(struct.pack("<I", len(self.dlgs)))
+        for grp in self.dlgs:
+            f.write(struct.pack("<3I", grp.idx, len(grp.acts), grp.grp_arg1))
+        for grp in self.dlgs:
+            for act in grp.acts:
+                f.write(struct.pack("<2H3I", act.opcode, act.ref, 
+                    len(act.dlgs), act.arg1, act.arg2))
+            for act in grp.acts:
+                for dlg in act.dlgs:
+                    f.write(struct.pack("<3I", dlg.op_start, dlg.arg1, 
+                        dlg.arg2))
+        f.write(struct.pack("<I", len(self.dlgops)))
+        for op in self.dlgops:
+            f.write(struct.pack("<H2B", op.ref, op.arg, op.opcode))
+


Commit: bd67cfbdba1cdcf7ae5f7924e57305d81ce69d41
    https://github.com/scummvm/scummvm-tools/commit/bd67cfbdba1cdcf7ae5f7924e57305d81ce69d41
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fix doc

Changed paths:
    engines/petka/help/comp_dlg.txt
    engines/petka/help/comp_scr.txt


diff --git a/engines/petka/help/comp_dlg.txt b/engines/petka/help/comp_dlg.txt
index d9d334bb5..84d25bece 100644
--- a/engines/petka/help/comp_dlg.txt
+++ b/engines/petka/help/comp_dlg.txt
@@ -3,10 +3,11 @@
 romiq.kh at gmail.com
 v0.3 2014-06-03
 
-<i>Синтаксис файла</i>
+<u>Синтаксис файла</u>
+
+Команды указываются построчно, разбиение одной команды на несколько строк не 
+допускается. Часть строки после символа '#' считается комментарием.
 
-Команды указываеются построчно, разбиение одной команды на несколько строк.
-Часть строки после символа '#' считается комментарием.
 
 Обозначения:
  * \<обязательные параметры\>
@@ -18,7 +19,7 @@ v0.3 2014-06-03
  * MSG - сообщения, попадают в DIALOGUE.LOD
  * DLGGRP - группы сообщений, попадают в DIALOGUE.FIX
  
-<i>MSG</i>
+<u>MSG</u>
 
 Каждая запись должна состоять из 2х строк. В второй строке должна быть только 
 одна запись.
@@ -29,7 +30,7 @@ MSG \<идентификатор объекта\> \<"имя файла wav"\> [\
 Аргументы - число от 0 до 0xffffffff. Аргументы могут отутствовать, в этом 
 случае они будут заменены 0.
 
-<i>DLGGRP</i>
+<u>DLGGRP</u>
 
 DLGGRP \<номер группы\> [\<аргумент\>]
   [обработчики - ON]
@@ -38,7 +39,7 @@ ENDDLGGRP
 Аргумент - число от 0 до 0xffffffff. Аргумент может отутствовать, в этом 
 случае он будет заменён 0.
 
-<i>ON</i>
+<u>ON</u>
 
 ON \<код операции\> \<идентификатор объекта\> [\<аргумент 1\> [\<аргумент 2\>]]
   [список - DLG]
@@ -52,7 +53,7 @@ ENDON
 Аргументы - числа от 0 до 0xffffffff. Аргументы могут отутствовать, в этом 
 случае они будут заменены 0.
 
-<i>DLG</i>
+<u>DLG</u>
 
 DLGSET [\<аргумент 1\> [\<аргумент 2\>]]
   [операции]
@@ -62,7 +63,7 @@ ENDDLGSET
 Аргументы - число от 0 до 0xffffffff. Аргументы могут отутствовать, в этом 
 случае они будут заменены 0.
 
-<i>операции</i>
+<u>операции</u>
 
 \<код операции\> [\<аргумент\> [\<ссылка на сообщение\>]]
 
diff --git a/engines/petka/help/comp_scr.txt b/engines/petka/help/comp_scr.txt
index ccf54f183..f9199f64d 100644
--- a/engines/petka/help/comp_scr.txt
+++ b/engines/petka/help/comp_scr.txt
@@ -3,10 +3,10 @@
 romiq.kh at gmail.com
 v0.3 2014-06-03
 
-<i>Синтаксис файла</i>
+<u>Синтаксис файла</u>
 
-Команды указываеются построчно, разбиение одной команды на несколько строк.
-Часть строки после символа '#' считается комментарием.
+Команды указываются построчно, разбиение одной команды на несколько строк не 
+допускается. Часть строки после символа '#' считается комментарием.
 
 Обозначения:
  * \<обязательные параметры\>
@@ -19,24 +19,24 @@ v0.3 2014-06-03
  * SCENE - сцены, попадают во вторую половину SCRIPT.DAT
  * RES - ресурсы, попадают в RESOURCE.QRC
  
-<i>OBJ</i>
+<u>OBJ</u>
 
 OBJ \<идентификатор объекта\> \<номер 0..0xffff\> "\<Имя\>"
   [обработчики событий - ON]
 ENDOBJ
 
-<i>SCENE</i>
+<u>SCENE</u>
 
 SCENE \<идентификатор сцены\> \<номер 0..0xffff\> "\<Имя\>"
   [обработчики событий - ON]
   [ссылки на другие объекты - REF]
 ENDSCENE
 
-<i>RES</i>
+<u>RES</u>
 
 RES \<идентификатор ресурса\> \<номер 0..0xffff\> "\<путь\>"
 
-<i>ON</i>
+<u>ON</u>
 
 Обработчик события. Вызывается при применении на объекте операции.
 При загрузке скрипта ко всем объектам и сценам применяется операция TOTALINIT.
@@ -47,19 +47,19 @@ ON \<операция\> [\<статус 0..0xff\> THIS]
   [операции]
 ENDON
 
-<i>REF</i>
+<u>REF</u>
 
 REF \<ссылка на объект\> [\<аргумент 1\> [\<аргумент 2\> [\<аргумент 3\> [\<аргумент 4\> [\<аргумент 5\>]]]]]
 
 Отсутствующие аргументы будут заменены на -1 (0xffffffff).
  
-<i>ZEROREF</i>
+<u>ZEROREF</u>
 
 Используется если запись в backgrnd.bg необходима, но ни один из объектов не 
 добавлен к сцене. Если у сцены уже были указаны REF использвание этого операнда 
 невозможно.
 
-<i>операции</i>
+<u>операции</u>
 
 \<код операции\> \<ссылка на объект\> [\<аргумент 1\> [\<аргумент 2\> [\<аргумент 3\>]]]
 


Commit: ce8130943deac3b6cb751f3d1c63f09a7b2bfe9e
    https://github.com/scummvm/scummvm-tools/commit/ce8130943deac3b6cb751f3d1c63f09a7b2bfe9e
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Fixes

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/help/comp_dlg.txt
    engines/petka/help/comp_scr.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index cab09d1e3..f791b8b0f 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -7,6 +7,9 @@
   BACKGRND.BG, DIALOGUE.FIX, DIALOGUE.FIX
 Выполнен рефакторинг кода
 Обновлена справка
+Добавлено сохранение переведённых DIALOGUE.LOD (субтитры) и NAMES.INI (надписи)
+Исправлено сохранение шаблонов для перевода в транслите (буквы "а" и "Э")
+Если перевод загружен - то сообщения в списке тоже будут переведены.
 
 2014-05-23 версия 0.2o
 ----------------------
@@ -14,7 +17,7 @@
 
 2014-05-21 версия 0.2n
 ----------------------
-Функуция транслитерации заменена на собственную.
+Функция транслитерации заменена на собственную.
 
 2014-05-21 версия 0.2m
 ----------------------
diff --git a/engines/petka/help/comp_dlg.txt b/engines/petka/help/comp_dlg.txt
index 84d25bece..02c076f40 100644
--- a/engines/petka/help/comp_dlg.txt
+++ b/engines/petka/help/comp_dlg.txt
@@ -45,7 +45,9 @@ ON \<код операции\> \<идентификатор объекта\> [\<
   [список - DLG]
 ENDON
 
-Код операции - код операции (см. <a href="/help/comp_scr">Синтаксис компиляции SCRIPT.DAT</a>).
+Код операции - код операции при выполнении которой будет выполнен обработчик
+ (см. <a href="/help/comp_scr">Синтаксис компиляции SCRIPT.DAT</a>).
+Также есть специальный код 0xFFFE.
 
 Идентификатор объекта - идентификатор объекта к которому была применена 
 операция.
@@ -55,10 +57,10 @@ ENDON
 
 <u>DLG</u>
 
-DLGSET [\<аргумент 1\> [\<аргумент 2\>]]
+DLG [\<аргумент 1\> [\<аргумент 2\>]]
   [операции]
   [\<метка перехода\>:]
-ENDDLGSET
+ENDDLG
 
 Аргументы - число от 0 до 0xffffffff. Аргументы могут отутствовать, в этом 
 случае они будут заменены 0.
@@ -74,12 +76,12 @@ ENDDLGSET
 
 Операции:
 
- * BREAK
- * MENU
- * GOTO
- * MENURET
- * RETURN
- * PLAY
- * CIRCLE
+ * 1 - BREAK
+ * 2 - MENU
+ * 3 - GOTO
+ * 4 - MENURET
+ * 6 - RETURN
+ * 7 - PLAY
+ * 8 - CIRCLE
  * число 0..0xff
 
diff --git a/engines/petka/help/comp_scr.txt b/engines/petka/help/comp_scr.txt
index f9199f64d..720de8fac 100644
--- a/engines/petka/help/comp_scr.txt
+++ b/engines/petka/help/comp_scr.txt
@@ -51,11 +51,14 @@ ENDON
 
 REF \<ссылка на объект\> [\<аргумент 1\> [\<аргумент 2\> [\<аргумент 3\> [\<аргумент 4\> [\<аргумент 5\>]]]]]
 
+Указывает на объект находящийся на сцене. Эти записи попадают в файл
+  BACKGRND.BG.
+
 Отсутствующие аргументы будут заменены на -1 (0xffffffff).
  
 <u>ZEROREF</u>
 
-Используется если запись в backgrnd.bg необходима, но ни один из объектов не 
+Используется если запись в BACKGRND.BG необходима, но ни один из объектов не 
 добавлен к сцене. Если у сцены уже были указаны REF использвание этого операнда 
 невозможно.
 
@@ -69,63 +72,63 @@ REF \<ссылка на объект\> [\<аргумент 1\> [\<аргумен
 
 Операции:
 
- * USE
- * SETPOS
- * GOTO
- * LOOK
- * SAY
- * TAKE
- * WALK
- * TALK
- * END
- * SET
- * SHOW
- * HIDE
- * DIALOG
- * ZBUFFER
- * TOTALINIT
- * ANIMATE
- * STATUS
- * ADDINV
- * DELINV
- * STOP
- * CURSOR
- * OBJECTUSE
- * ACTIVE
- * SAID
- * SETSEQ
- * ENDSEQ
- * CHECK
- * IF
- * DESCRIPTION
- * HALF
- * WALKTO
- * WALKVICH
- * INITBG
- * USERMSG
- * SYSTEM
- * SETZBUFFER
- * CONTINUE
- * MAP
- * PASSIVE
- * NOMAP
- * SETINV
- * BGSFX
- * MUSIC
- * IMAGE
- * STAND
- * ON
- * OFF
- * PLAY
- * LEAVEBG
- * SHAKE
- * SP
- * RANDOM
- * JUMP
- * JUMPVICH
- * PART
- * CHAPTER
- * AVI
- * TOMAP
+ *  1 - USE
+ *  2 - SETPOS
+ *  3 - GOTO
+ *  4 - LOOK
+ *  5 - SAY
+ *  6 - TAKE
+ *  9 - WALK
+ * 10 - TALK
+ * 11 - END
+ * 14 - SET
+ * 15 - SHOW
+ * 16 - HIDE
+ * 17 - DIALOG
+ * 18 - ZBUFFER
+ * 19 - TOTALINIT
+ * 20 - ANIMATE
+ * 21 - STATUS
+ * 22 - ADDINV
+ * 23 - DELINV
+ * 24 - STOP
+ * 25 - CURSOR
+ * 26 - OBJECTUSE
+ * 27 - ACTIVE
+ * 28 - SAID
+ * 29 - SETSEQ
+ * 30 - ENDSEQ
+ * 31 - CHECK
+ * 32 - IF
+ * 33 - DESCRIPTION
+ * 34 - HALF
+ * 36 - WALKTO
+ * 37 - WALKVICH
+ * 38 - INITBG
+ * 39 - USERMSG
+ * 40 - SYSTEM
+ * 41 - SETZBUFFER
+ * 42 - CONTINUE
+ * 43 - MAP
+ * 44 - PASSIVE
+ * 45 - NOMAP
+ * 46 - SETINV
+ * 47 - BGSFX
+ * 48 - MUSIC
+ * 49 - IMAGE
+ * 50 - STAND,
+ * 51 - ON
+ * 52 - OFF
+ * 53 - PLAY
+ * 54 - LEAVEBG
+ * 55 - SHAKE
+ * 56 - SP
+ * 57 - RANDOM
+ * 58 - JUMP
+ * 59 - JUMPVICH
+ * 60 - PART
+ * 61 - CHAPTER
+ * 62 - AVI
+ * 63 - TOMAP
  * число 0..0xffff
 
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index ce51ef530..cab797b6b 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -28,7 +28,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.2o 2014-05-23"
+VERSION = "v0.3 2014-06-03"
 
 def hlesc(value):
     if value is None:
@@ -73,7 +73,7 @@ def translit(text):
     allcaps = True
     for ch in text:
         ps = ru.find(ch)
-        if ps > 0:
+        if ps >= 0:
             ret += en[ps]
         elif ch in sl:
             ret += sl[ch]
@@ -361,6 +361,13 @@ class App(tkinter.Frame):
             menutran.add_command(
                     command = self.on_tran_load,
                     label = "Load translation (.po)")
+            menutran.add_separator()
+            menutran.add_command(
+                    command = self.on_tran_save_lod,
+                    label = "Save DIALOGUE.LOD")
+            menutran.add_command(
+                    command = self.on_tran_save_names,
+                    label = "Save NAMES.INI")
 
         self.menuhelp = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menuhelp,
@@ -1444,8 +1451,10 @@ class App(tkinter.Frame):
             self.update_gui("Messages ({})".format(len(self.sim.msgs)))
             for idx, msg in enumerate(self.sim.msgs):
                 capt = msg.name
-                if len(capt) > 25:
-                    capt = capt[:25] + "|"
+                if self.tran:
+                    capt = self._t(msg.name, "msg")
+                if len(capt) > 40:
+                    capt = capt[:40] + "|"
                 self.insert_lb_act("{} - {}".format(msg.idx, capt),
                     ["msgs", idx], idx)
         # change
@@ -1902,6 +1911,70 @@ class App(tkinter.Frame):
             self.add_info("Error opening \"{}\" \n\n{}".\
                 format(hlesc(fn), hlesc(traceback.format_exc())))
 
+    def on_tran_save_lod(self):
+        # save dialog
+        if not self.sim: return
+        if len(self.sim.msgs) == 0 or not self.tran:
+            self.switch_view(0)
+            self.clear_info()
+            self.add_info("No messages or translations loaded")
+            return        
+        fn = filedialog.asksaveasfilename(parent = self,
+            title = "Save DIALOGUE.LOD",
+            filetypes = [('DIALOGUE.LOD', ".lod"), ('all files', '.*')],
+            initialdir = os.path.abspath(os.curdir))
+        if not fn: return # save canceled
+        # save template
+        try:
+            sim2 = petka.Engine()
+            sim2.init_empty("cp1251")
+            for msg in self.sim.msgs:
+                nmsg = petka.engine.MsgObject(msg.idx, msg.msg_wav, 
+                    msg.msg_arg1, msg.msg_arg2, msg.msg_arg3)
+                nmsg.name = self._t(msg.name, "msg")
+                sim2.msgs.append(nmsg)
+            f = open(fn, "wb")
+            try:
+                sim2.write_lod(f)
+            finally:
+                f.close()
+        except:
+            self.switch_view(0)
+            self.clear_info()
+            self.add_info("Error saving \"{}\" \n\n{}".\
+                format(hlesc(fn), hlesc(traceback.format_exc())))
+
+    def on_tran_save_names(self):
+        # save dialog
+        if not self.sim: return
+        if len(self.sim.msgs) == 0 or not self.tran:
+            self.switch_view(0)
+            self.clear_info()
+            self.add_info("No messages or translations loaded")
+            return        
+        fn = filedialog.asksaveasfilename(parent = self,
+            title = "Save NAMES.INI",
+            filetypes = [('NAMES.INI', ".ini"), ('all files', '.*')],
+            initialdir = os.path.abspath(os.curdir))
+        if not fn: return # save canceled
+        # save template
+        try:
+            f = open(fn, "wb")
+            def write(msg):
+                f.write("{}\n".format(msg).encode("cp1251"))
+            try:
+                write("[all]")
+                for name in self.sim.namesord:
+                    write(name + "=" + self._t(self.sim.names[name], "name"))
+            finally:
+                f.close()
+        except:
+            self.switch_view(0)
+            self.clear_info()
+            self.add_info("Error saving \"{}\" \n\n{}".\
+                format(hlesc(fn), hlesc(traceback.format_exc())))
+
+
 def main():
     root = tkinter.Tk()
     app = App(master = root)


Commit: 2e1f405a36c027a203616b40f61bdb1ad67f1743
    https://github.com/scummvm/scummvm-tools/commit/2e1f405a36c027a203616b40f61bdb1ad67f1743
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Fixes

Changed paths:
    engines/petka/dist/setup.py
    engines/petka/help/translate.txt


diff --git a/engines/petka/dist/setup.py b/engines/petka/dist/setup.py
index fabaabf85..f3dd7fcce 100644
--- a/engines/petka/dist/setup.py
+++ b/engines/petka/dist/setup.py
@@ -17,7 +17,7 @@ buildOptions = dict(packages = ["re", "io", "PIL", "traceback", "zlib", "gzip",
 executables = [
     Executable('p12explore.py',
         base = 'Win32GUI',
-        targetName = "p12explore.exe")
+        targetName = "p12explore.exe"),
     Executable('p12script.py',
         base = 'Console',
         targetName = "p12script.exe")
diff --git a/engines/petka/help/translate.txt b/engines/petka/help/translate.txt
index c059d68b3..1b883ba26 100644
--- a/engines/petka/help/translate.txt
+++ b/engines/petka/help/translate.txt
@@ -6,8 +6,15 @@
  * Создать шаблон в транслите формате POT 
    (Translation->Save transliterate template)
  * Загрузить перевод из файла PO (Translation->Save translation)
- * Покаывать перевод названий и текстов у всех объектов
- 
+ * Сохранить перевод субтитров (файл DIALOGUE.LOD)
+ * Сохранить перевод названий объектов (файл NAMES.INI)
+
+Если перевод загружен программа будет автоматически показывать перевод 
+  названий и текстов у всех объектов.
+
+Примечание: для того чтобы файлы DIALOGUE.LOD и NAMES.INI подгружались во время
+  работы необходимо предварительно распаковать MAIN.STR.
+
 Для работы этих функций необходима библиотека polib.
  
  * https://pypi.python.org/pypi/polib


Commit: 75743464dbc7d8a11c8d562f53677e23ff53c0e9
    https://github.com/scummvm/scummvm-tools/commit/75743464dbc7d8a11c8d562f53677e23ff53c0e9
Author: romiq (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: license.txt отредактирован онлайн на Bitbucket

Changed paths:
    engines/petka/help/license.txt


diff --git a/engines/petka/help/license.txt b/engines/petka/help/license.txt
index fbdb990e3..f70526734 100644
--- a/engines/petka/help/license.txt
+++ b/engines/petka/help/license.txt
@@ -1,4 +1,4 @@
-Лицении
+Лицензии
 
 Данная программа поставляется под лицензией MIT.
 


Commit: 8aa21d6ce28cbedb8d67bfbfa26d620da0176c7d
    https://github.com/scummvm/scummvm-tools/commit/8aa21d6ce28cbedb8d67bfbfa26d620da0176c7d
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Add opcodes and dialog opcodes statistic

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index cab797b6b..62e7140e4 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -263,6 +263,8 @@ class App(tkinter.Frame):
         self.path_handler["msgs"] = self.path_msgs
         self.path_handler["dlgs"] = self.path_dlgs
         self.path_handler["casts"] = self.path_casts
+        self.path_handler["opcodes"] = self.path_opcodes
+        self.path_handler["dlgops"] = self.path_dlgops
         self.path_handler["test"] = self.path_test
         self.path_handler["about"] = self.path_about
         self.path_handler["support"] = self.path_support
@@ -329,6 +331,12 @@ class App(tkinter.Frame):
         self.menuedit.add_command(
                 command = lambda: self.open_path("/dlgs"),
                 label = "Dialog groups")
+        self.menuedit.add_command(
+                command = lambda: self.open_path("/opcodes"),
+                label = "Opcodes")
+        self.menuedit.add_command(
+                command = lambda: self.open_path("/dlgops"),
+                label = "Dialog opcodes")
 
         self.menunav = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menunav,
@@ -763,11 +771,16 @@ class App(tkinter.Frame):
             return self.tran["_"][value]
         return value
 
-    def fmt_opcode(self, opcode):
+    def fmt_opcode(self, opcode, nofmt = False):
+        if nofmt:
+            return petka.OPCODES.get(opcode, ["OP{:04X}".format(opcode)])[0]
         return petka.OPCODES.get(opcode, ["<font color=\"red\">OP{:04X}</font>".\
             format(opcode)])[0]
 
-    def fmt_dlgop(self, opcode):
+    def fmt_dlgop(self, opcode, nofmt = False):
+        if nofmt:
+            return petka.DLGOPS.get(opcode, ["OP{:02X}".\
+                format(opcode)])[0]
         return petka.DLGOPS.get(opcode, ["<font color=\"red\">OP{:02X}</font>".\
             format(opcode)])[0]
 
@@ -853,6 +866,9 @@ class App(tkinter.Frame):
                 scn = self.fmt_hl_scene(scene.idx, True)
                 break
         self.add_info("  Start scene:   {}\n".format(scn))
+        self.add_info("\n")
+        self.add_info("  <a href=\"/opcodes\">Opcodes</a>\n")
+        self.add_info("  <a href=\"/dlgops\">Dialog opcodes</a>\n")
     
 
     def path_default(self, path):
@@ -878,6 +894,8 @@ class App(tkinter.Frame):
                 ("Casts ({})".format(len(self.sim.casts)), "/casts"),
                 ("Messages ({})".format(len(self.sim.msgs)), "/msgs"),
                 ("Dialog groups ({})".format(len(self.sim.dlgs)), "/dlgs"),
+                ("Opcodes", "/opcodes"),
+                ("Dialog opcodes", "/dlgops"),
                 #("-", None),
                 #("Test image", ["test", "image"]),
                 #("Test info", ["test","info"]),
@@ -1413,7 +1431,6 @@ class App(tkinter.Frame):
         return self.path_std_items(path, 1, "Invntr", "invntr", "obj", 
             self.sim.invntr, self.sim.invntrord, 0, info)
 
-
     def path_casts(self, path):
         if self.sim is None:
             return self.path_default([])
@@ -1643,6 +1660,215 @@ class App(tkinter.Frame):
             self.add_info("\n<b>Used by scenes</b>:\n")
             usedby(self.sim.scenes)
         
+    def path_opcodes(self, path):
+        if self.sim is None:
+            return self.path_default([])
+        self.switch_view(0)
+        keys = None
+        def keyslist():
+            opstat = {} # opcpdes count
+            acstat = {} # handlers count
+            dastat = {} # dialog handlers count
+            keys = list(petka.OPCODES.keys())
+            for rec in self.sim.objects + self.sim.scenes:
+                for act in rec.acts:
+                    acstat[act.act_op] = acstat.get(act.act_op, 0) + 1
+                    if act.act_op not in keys:
+                        keys.append(act.act_op)
+                    for op in act.ops:
+                        opstat[op.op_code] = opstat.get(op.op_code, 
+                            0) + 1
+                        if op.op_code not in keys:
+                            keys.append(op.op_code)
+            for grp in self.sim.dlgs:
+                for act in grp.acts:
+                    dastat[act.opcode] = dastat.get(act.opcode, 0) + 1
+                    if act.opcode not in keys:
+                        keys.append(act.opcode)
+            keys.sort()
+            return keys, opstat, acstat, dastat
+        if self.last_path[:1] != ("opcodes",):
+            # calc statistics
+            keys, opstat, acstat, dastat = keyslist()
+            self.update_gui("Opcodes ({})".format(len(keys)))
+            for key in keys:
+                self.insert_lb_act("{} - {}".format(key, self.fmt_opcode(key, 
+                    True)), ["opcodes", key], key)
+        # change
+        opcode = None
+        if len(path) > 1:
+            # index
+            self.select_lb_item(path[1])
+            try:
+                opcode = path[1]
+            except:
+                pass
+        else:
+            self.select_lb_item(None)
+        # display
+        self.clear_info()
+        if not opcode:
+            if len(path) > 1:
+                self.add_info("<b>Opcode </b> \"{}\" not found\n\n".format(
+                    path[1]))
+            self.add_info("<b>Opcodes</b>\n\n")
+            # display
+            if not keys:
+                keys, opstat, acstat, dastat = keyslist()
+            for key in keys:
+                opname = self.fmt_opcode(key)
+                msg = "  {:2} (0x{:02X})".format(key, key,
+                    opname)
+                mcnt = len(msg)
+                msg += " - {}".format(fmt_hl("/opcodes/{}".format(
+                    key), opname))
+                mcnt += len(self.fmt_opcode(key, True))    
+                while mcnt < 23:
+                    msg += " "
+                    mcnt += 1
+                msg += "{:4d}  {:4d}  {:4d}".format(
+                    opstat.get(key, 0),
+                    acstat.get(key, 0),
+                    dastat.get(key, 0))
+                self.add_info(msg + "\n")
+        else:
+            # grp info
+            self.add_info("<b>Opcode {}</b>\n\n".format(
+                self.fmt_opcode(opcode)))
+            ops = []
+            acts = []
+            dacts = []
+            for rec in self.sim.objects + self.sim.scenes:
+                for aidx, act in enumerate(rec.acts):
+                    if act.act_op == opcode:
+                        acts.append([rec.idx, aidx])
+                    for oidx, op in enumerate(act.ops):
+                        if op.op_code == opcode:
+                            ops.append([rec.idx, aidx, oidx])
+            for gidx, grp in enumerate(self.sim.dlgs):
+                for aidx, act in enumerate(grp.acts):
+                    if act.opcode == opcode and act.ref not in dacts:
+                        dacts.append([act.ref, gidx, aidx])
+            # display
+            if len(ops) == 0:
+                self.add_info("<i>Not used in scripts</i>\n\n")
+            else:            
+                self.add_info("<i>Used in scripts</i>: {}\n".format(len(ops)))
+                for idx, (obj_idx, aidx, oidx) in enumerate(ops):
+                    self.add_info("  {}) obj={}, act={}, op={} {}\n".format(
+                        idx, self.fmt_hl_obj_scene(obj_idx, False), aidx, oidx,
+                        self.fmt_cmt("// " + self.fmt_hl_obj_scene(obj_idx, 
+                        True))))
+                self.add_info("\n")  
+
+            if len(acts) == 0:
+                self.add_info("<i>Not used in handlers</i>\n\n")
+            else:            
+                self.add_info("<i>Used in handlers</i>: {}\n".format(len(acts)))
+                for idx, (obj_idx, aidx) in enumerate(acts):
+                    self.add_info("  {}) obj={}, act={} {}\n".format(
+                        idx, self.fmt_hl_obj_scene(obj_idx, False), aidx, 
+                        self.fmt_cmt("// " + self.fmt_hl_obj_scene(obj_idx, 
+                        True))))
+                self.add_info("\n")  
+
+            if len(dacts) == 0:
+                self.add_info("<i>Not used in dialog handlers</i>\n\n")
+            else:            
+                self.add_info("<i>Used in dialog handlers</i>: {}\n".format(
+                    len(dacts)))
+                for idx, (obj_idx, gidx, aidx) in enumerate(dacts):
+                    self.add_info("  {}) obj={}, group=<a href=\"/dlgs/{}\">{}"
+                        "</a>, act={} {}\n".format(
+                        idx, self.fmt_hl_obj_scene(obj_idx, False), gidx, gidx, 
+                        aidx, self.fmt_cmt("// " + self.fmt_hl_obj_scene(
+                        obj_idx, True))))
+                self.add_info("\n")  
+
+    def path_dlgops(self, path):
+        if self.sim is None:
+            return self.path_default([])
+        self.switch_view(0)
+        keys = None
+        def keyslist():
+            dlstat = {} # dialog opcodes count
+            keys = list(petka.DLGOPS.keys()) + [5, 9]
+            for grp in self.sim.dlgs:
+                for act in grp.acts:
+                    for dlg in act.dlgs:
+                        for op in dlg.ops:
+                            dlstat[op.opcode] = dlstat.get(op.opcode, 0) + 1
+                            if op.opcode not in keys:
+                                keys.append(op.opcode)
+            keys.sort()
+            return keys, dlstat
+        if self.last_path[:1] != ("dlgops",):
+            # calc statistics
+            keys, dlstat = keyslist()
+            self.update_gui("Dialog opcodes ({})".format(len(keys)))
+            for key in keys:
+                self.insert_lb_act("{} - {}".format(key, self.fmt_dlgop(key, 
+                    True)), ["dlgops", key], key)
+        # change
+        opcode = None
+        if len(path) > 1:
+            # index
+            self.select_lb_item(path[1])
+            try:
+                opcode = path[1]
+            except:
+                pass
+        else:
+            self.select_lb_item(None)
+        # display
+        self.clear_info()
+        if not opcode:
+            if len(path) > 1:
+                self.add_info("<b>Dialog opcode </b> \"{}\" not found\n\n".\
+                    format(path[1]))
+            self.add_info("<b>Dialog opcodes</b>\n\n")
+            # display
+            if not keys:
+                keys, dlstat = keyslist()
+            for key in keys:
+                opname = self.fmt_dlgop(key)
+                msg = "  {:2} (0x{:02X})".format(key, key, opname)
+                mcnt = len(msg)
+                msg += " - {}".format(fmt_hl("/dlgops/{}".format(
+                    key), opname))
+                mcnt += len(self.fmt_dlgop(key, True))    
+                while mcnt < 20:
+                    msg += " "
+                    mcnt += 1
+                msg += "{:4d}".format(dlstat.get(key, 0))
+                self.add_info(msg + "\n")
+        else:
+            # grp info
+            self.add_info("<b>Dialog opcode {}</b>\n\n".format(
+                self.fmt_dlgop(opcode)))
+            dls = []
+            for gidx, grp in enumerate(self.sim.dlgs):
+                for aidx, act in enumerate(grp.acts):
+                    for didx, dlg in enumerate(act.dlgs):
+                        for oidx, op in enumerate(dlg.ops):
+                            if op.opcode == opcode:
+                                dls.append([act.ref, gidx, aidx, didx, oidx])
+                            
+
+            # display
+            if len(dls) == 0:
+                self.add_info("<i>Not used in dialogs</i>\n\n")
+            else:            
+                self.add_info("<i>Used in dialogs</i>: {}\n".format(len(dls)))
+                for idx, (obj_idx, gidx, aidx, didx, oidx) in enumerate(dls):
+                    self.add_info("  {}) obj={}, group=<a href=\"/dlgs/{}\">{}"
+                        "</a>, act={}, dlg={}, op={} {}\n".format(
+                        idx, self.fmt_hl_obj_scene(obj_idx, False), gidx, gidx, 
+                        aidx, didx, oidx, self.fmt_cmt("// " + 
+                        self.fmt_hl_obj_scene(obj_idx, True))))
+                self.add_info("\n")  
+
+        
     def path_test(self, path):
         self.update_gui("Test {}".format(path[1]))
         self.insert_lb_act("Outline", [])


Commit: 736358e9a05f0f6fb056abcef51e56731383a2e8
    https://github.com/scummvm/scummvm-tools/commit/736358e9a05f0f6fb056abcef51e56731383a2e8
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Fix group display

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 62e7140e4..19c67e03d 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1745,10 +1745,10 @@ class App(tkinter.Frame):
                     for oidx, op in enumerate(act.ops):
                         if op.op_code == opcode:
                             ops.append([rec.idx, aidx, oidx])
-            for gidx, grp in enumerate(self.sim.dlgs):
+            for grp in self.sim.dlgs:
                 for aidx, act in enumerate(grp.acts):
                     if act.opcode == opcode and act.ref not in dacts:
-                        dacts.append([act.ref, gidx, aidx])
+                        dacts.append([act.ref, grp.idx, aidx])
             # display
             if len(ops) == 0:
                 self.add_info("<i>Not used in scripts</i>\n\n")
@@ -1847,12 +1847,12 @@ class App(tkinter.Frame):
             self.add_info("<b>Dialog opcode {}</b>\n\n".format(
                 self.fmt_dlgop(opcode)))
             dls = []
-            for gidx, grp in enumerate(self.sim.dlgs):
+            for grp in self.sim.dlgs:
                 for aidx, act in enumerate(grp.acts):
                     for didx, dlg in enumerate(act.dlgs):
                         for oidx, op in enumerate(dlg.ops):
                             if op.opcode == opcode:
-                                dls.append([act.ref, gidx, aidx, didx, oidx])
+                                dls.append([act.ref, grp.idx, aidx, didx, oidx])
                             
 
             # display


Commit: a954806273fa8570c43e2246fd587b5d492a8ce0
    https://github.com/scummvm/scummvm-tools/commit/a954806273fa8570c43e2246fd587b5d492a8ce0
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Fix changes

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index f791b8b0f..a5296a752 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,6 +1,10 @@
 Что нового
 ==========
 
+2014-06-06 версия 0.3a
+----------------------
+Добавлена статистика использования опкодов скрипта и опкодов диалогов.
+
 2014-06-03 версия 0.3
 ---------------------
 Добавлен консольный компилятор и декомпилятор для файлов SCRIPT.DAT, 
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 19c67e03d..9777972b3 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -28,7 +28,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.3 2014-06-03"
+VERSION = "v0.3a 2014-06-06"
 
 def hlesc(value):
     if value is None:
@@ -1853,7 +1853,6 @@ class App(tkinter.Frame):
                         for oidx, op in enumerate(dlg.ops):
                             if op.opcode == opcode:
                                 dls.append([act.ref, grp.idx, aidx, didx, oidx])
-                            
 
             # display
             if len(dls) == 0:


Commit: b33be223a70f58db183518c82f7ffc647d3d3f17
    https://github.com/scummvm/scummvm-tools/commit/b33be223a70f58db183518c82f7ffc647d3d3f17
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Fix loading FLC with Pillow

Changed paths:
    engines/petka/petka/imgflc.py


diff --git a/engines/petka/petka/imgflc.py b/engines/petka/petka/imgflc.py
index c692db697..1af7377a2 100644
--- a/engines/petka/petka/imgflc.py
+++ b/engines/petka/petka/imgflc.py
@@ -16,6 +16,34 @@ try:
 except ImportError:
     pass
 
+FLC_HEADER = [
+    ["fsize",        1, "I", False],
+    ["ftype",        1, "H", False],
+    ["frames_num",   1, "H", True],
+    ["width",        1, "H", True],
+    ["height",       1, "H", True],
+    ["depth",        1, "H", False],
+    ["flags",        1, "H", True],
+    ["speed",        1, "I", True],
+    ["reserved1",    1, "H", False],
+    ["created",      1, "I", True],
+    ["creator",      1, "I", True],
+    ["updated",      1, "I", True],
+    ["updater",      1, "I", True],
+    ["aspect_dx",    1, "H", True],
+    ["aspect_dy",    1, "H", True],
+    ["ext_flags",    1, "H", False],
+    ["keyframes",    1, "H", False],
+    ["totalframes",  1, "H", False],
+    ["req_memory",   1, "I", False],
+    ["max_regions",  1, "H", False],
+    ["transp_num",   1, "H", False],
+    ["reserved2",   24, "s", False],
+    ["oframe1",      1, "I", False],
+    ["oframe2",      1, "i", False],
+    ["reserved3",   40, "s", False],
+]
+
 class FLCLoader:
     def __init__(self):
         self.rgb = None
@@ -36,6 +64,149 @@ class FLCLoader:
         except EOFError:
             pass # end of sequence
         
+        
+    def parseflcchunks(self, f, offset, limit, level = 0, maxchunks = None,):
+        def check_hdr(size, delta, name, offset):
+            if delta < size:
+                raise EngineError("Incorrect FLC %s chunk at 0x{:08x}".format(
+                    (name, offset)))
+        
+        chunks = []
+        while True:
+            if limit is not None:
+                if offset >= limit: 
+                    break
+            if maxchunks is not None:
+                if len(chunks) >= maxchunks: 
+                    break
+            chunk = {"offset": offset}  
+            temp = f.read(6)
+            sz, tp = struct.unpack_from("<IH", temp)
+            offset += 6
+
+            chunk["size"] = sz
+            chunk["type"] = tp
+            delta = sz - 6
+            if delta < 0:
+                raise EngineError("Incorrect FLC chunk at 0x{:08x}".format(
+                    chunk["offset"]))
+            
+            raw_chunks = [
+                0x4, # COLOR_256
+                0x7, # DELTA_FLC
+                0xf, # BYTE_RUN
+                0xF100, # PREFIX_TYPE - mismaked, out destination ignore this
+            ]
+            #print("{}CHUNK 0x{:x}, size = {}".format("  "*level, tp, sz))
+            # do not parse 3rd level 0x12 chunk
+            if tp == 0x12 and level == 2:
+                tp = 0x4
+
+            # parse chunks
+            if tp in raw_chunks:
+                temp = f.read(delta)
+                offset += delta
+                chunk["data"] = temp
+            elif tp == 0x12:
+                # PSTAMP
+                check_hdr(6, delta, "PSTAMP", offset)
+                temp = f.read(6)
+                delta -= 6
+                height, width, xlate = struct.unpack_from("<3H", temp)
+                offset += 6
+                offset, subchunks = self.parseflcchunks(f, offset, 
+                    offset + delta, level + 1, 1)
+                chunk["chunks"] = subchunks
+                #print(subchunks)
+            elif tp == 0xF1FA:
+                # FRAME_TYPE
+                check_hdr(10, delta, "FRAME_TYPE", offset)
+                temp = f.read(10)
+                delta -= 10
+                sub_num, delay, reserved, width, height = \
+                    struct.unpack_from("<5H", temp)
+                offset += 10
+                chunk["delay"] = delay
+                chunk["width"] = width
+                chunk["height"] = height
+                offset, subchunks = self.parseflcchunks(f, offset, 
+                    offset + delta, level + 1, sub_num)
+                chunk["chunks"] = subchunks
+            else:
+                raise Exception("Unknown FLC chunk type 0x{:04x} at 0x{:x08x}".\
+                    format(tp, offset))
+            
+            chunks.append(chunk)
+            
+        return offset, chunks
+        
     def load_data(self, f):
-        self.image = Image.open(f)
+        # parse header
+        offset = 0
+        hdr_keys = []
+        hdr_struct = "<"
+        for hnam, hsz, htp, hed in FLC_HEADER:
+            hdr_keys.append(hnam)
+            if hsz == 1:
+                hdr_struct += htp
+            else:
+                hdr_struct += "%d" % hsz + htp
+        
+        header = {}
+        temp = f.read(128)
+        hdr = struct.unpack_from(hdr_struct, temp)
+        
+        offset += 128
             
+        if len(hdr) != len(hdr_keys):
+            raise EngineError("Incorrect FLC header {} != {}".format(
+                len(hdr), len(hdr_keys)))
+        for hid in range(len(hdr)):
+            header[hdr_keys[hid]] = hdr[hid]
+        
+        if header["ftype"] != 0xAF12:
+            raise EngineError("Unsupported FLC type (0x{:04x})".format(
+                header["ftype"]))
+        
+        # check if not EGI ext
+        if header["creator"] == 0x45474900:
+            if header["ext_flags"] != 0:
+                raise EngineError("Unsupported FLC EGI extension")    
+        
+        # NOTE: we recreate FLC to avoid Pilllow bug
+        #  1. remove 0xf100 chunk  (PREFIX, implementation specific)
+        #  2. remobe 0x12 subchunk (PSTAMP) from 1st frame
+        
+        # read chunks
+        _, chunks = self.parseflcchunks(f, offset, header["fsize"])
+
+        f.seek(0)
+        buf = io.BytesIO()
+        buf.write(f.read(128)) # clone header
+        for chunk in chunks:
+            if chunk["type"] == 0xF100:
+                continue
+            elif chunk["type"] == 0xF1FA:
+                rebuild = False
+                nchunks = []
+                nsz = 16 # I6H - type, size, sub_num, delay, 
+                         # reserved, width, height
+                for schunk in chunk["chunks"]:
+                    if schunk["type"] == 0x12: # detect mailformed PSTAMP
+                        rebuild = True
+                    elif rebuild:
+                        nchunks.append(schunk)
+                        nsz += schunk["size"]
+                if rebuild:
+                    buf.write(struct.pack("<I6H", nsz, 0xF1FA, len(nchunks), 
+                        chunk["delay"], 0, chunk["width"], chunk["height"]))
+                    for schunk in nchunks:
+                        f.seek(schunk["offset"])
+                        buf.write(f.read(schunk["size"]))
+                    continue
+            # copy chunk
+            buf.write(f.read(chunk["size"]))
+
+        buf.seek(0)
+        self.image = Image.open(buf)
+


Commit: aa0c112ff3edbe9749608ddb255f2c99ced683c1
    https://github.com/scummvm/scummvm-tools/commit/aa0c112ff3edbe9749608ddb255f2c99ced683c1
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: changes

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index a5296a752..cf6cbd40b 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,6 +1,10 @@
 Что нового
 ==========
 
+2014-06-10 версия 0.3b
+----------------------
+Исправлена загрузка палитры FLC файлов.
+
 2014-06-06 версия 0.3a
 ----------------------
 Добавлена статистика использования опкодов скрипта и опкодов диалогов.
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 9777972b3..4dd0d4396 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -28,7 +28,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.3a 2014-06-06"
+VERSION = "v0.3b 2014-06-10"
 
 def hlesc(value):
     if value is None:


Commit: b380b5dfc8f01819521b25ac591418d9108638cd
    https://github.com/scummvm/scummvm-tools/commit/b380b5dfc8f01819521b25ac591418d9108638cd
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: add stores info

Changed paths:
  A engines/petka/help/strs.txt
    engines/petka/help/changes.txt
    engines/petka/help/cmdline.txt
    engines/petka/help/index.txt
    engines/petka/help/list
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index cf6cbd40b..c58820cd0 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,5 +1,9 @@
 Что нового
 ==========
+2014-12-20 версия 0.3c
+----------------------
+Отображение загруженных хранилищ (.str)
+Загрузка хранилищ из отдельных файлов
 
 2014-06-10 версия 0.3b
 ----------------------
diff --git a/engines/petka/help/cmdline.txt b/engines/petka/help/cmdline.txt
index 9e578f0dd..52d7c8549 100644
--- a/engines/petka/help/cmdline.txt
+++ b/engines/petka/help/cmdline.txt
@@ -2,8 +2,14 @@
 
 Синтаксис вызова программы из коммандной строки
 
+Открыть данные
+
   p12explore [-d путь к данным]|[-t путь к переводу]|[открываемый раздел]
 
+Открыть хранилище
+
+  p12explore [-s путь к .str]
+
 Путь к данным - путь к файлу или каталогу где находится файл с данными.
   
 Основные разделы:
@@ -16,6 +22,7 @@
  * /scenes - сцены
  * /msgs - сообщения
  * /dlgs - группы диалогов
+ * /strs - хранилища
  
 Так как после открытия данных данные о переводе удаляются то рекомендуется
   следующий порядок загрузки:
diff --git a/engines/petka/help/index.txt b/engines/petka/help/index.txt
index 1b5b8f78c..e159406ec 100644
--- a/engines/petka/help/index.txt
+++ b/engines/petka/help/index.txt
@@ -1,6 +1,6 @@
 Справка
 
-Версия: 0.3 2014-06-03
+Версия: 0.3c 2014-12-20
 
  * <a href="/help/changes">Что нового</a>
  * <a href="/help/faq">Часто задаваемые вопросы</a>
@@ -17,6 +17,7 @@
  * <a href="/help/casts">Цвета предметов</a>
  * <a href="/help/msgs">Собщения</a>
  * <a href="/help/dlgs">Группы диалогов</a>
+ * <a href="/help/strs">Хранилища</a>
 
 Прочая информация
 
diff --git a/engines/petka/help/list b/engines/petka/help/list
index 0bc2a2ba0..c8a65be21 100644
--- a/engines/petka/help/list
+++ b/engines/petka/help/list
@@ -11,6 +11,7 @@ invntr
 casts
 msgs
 dlgs
+strs
 translate
 cmdline
 support
diff --git a/engines/petka/help/strs.txt b/engines/petka/help/strs.txt
new file mode 100644
index 000000000..eca1839f6
--- /dev/null
+++ b/engines/petka/help/strs.txt
@@ -0,0 +1,5 @@
+Хранилища
+
+На этой странице можно просмотреть все загруженные хранилища и их содержимое.
+
+
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 4dd0d4396..22211bfa4 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -28,7 +28,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.3b 2014-06-10"
+VERSION = "v0.3c 2014-12-20"
 
 def hlesc(value):
     if value is None:
@@ -158,7 +158,7 @@ class App(tkinter.Frame):
         master.title(APPNAME)
         self.pack(fill = tkinter.BOTH, expand = 1)
         self.pad = None
-        self.sim = None
+        self.clear_data()
         # path
         if hasattr(sys, 'frozen'):
             self.app_path = sys.executable
@@ -184,13 +184,18 @@ class App(tkinter.Frame):
         self.need_update = False
         self.canv_view_fact = 1
         self.main_image = tkinter.PhotoImage(width = 1, height = 1)
-        # translation
-        self.tran = None
         # add on_load handler
         self.after_idle(self.on_first_display)
         
-    def create_widgets(self):
+    def clear_data(self):
+        self.sim = None
+        # store manager
+        self.strfm = None
+        # translation
+        self.tran = None
+    
         
+    def create_widgets(self):
         ttk.Style().configure("Tool.TButton", width = -1) # minimal width
         ttk.Style().configure("TLabel", padding = self.pad)
         ttk.Style().configure('Info.TFrame', background = 'white', \
@@ -270,6 +275,7 @@ class App(tkinter.Frame):
         self.path_handler["support"] = self.path_support
         self.path_handler["help"] = self.path_help
         self.path_handler["info"] = self.path_info
+        self.path_handler["strs"] = self.path_stores
         
         self.update_after()
         repath = "/about"
@@ -277,6 +283,9 @@ class App(tkinter.Frame):
             if cmd == "load":
                 self.open_data_from(arg)
                 repath = "/"
+            elif cmd == "str":
+                self.open_str_from(arg)
+                repath = "/"
             elif cmd == "tran":
                 self.open_tran_from(arg)
             elif cmd == "open":
@@ -296,6 +305,10 @@ class App(tkinter.Frame):
                 command = self.on_open_data,
                 label = "Open data...")
         self.menufile.add_separator()
+        self.menufile.add_command(
+                command = self.on_open_str,
+                label = "Open STR...")
+        self.menufile.add_separator()
         self.menufile.add_command(
                 command = self.on_exit,
                 label = "Quit")    
@@ -337,6 +350,9 @@ class App(tkinter.Frame):
         self.menuedit.add_command(
                 command = lambda: self.open_path("/dlgops"),
                 label = "Dialog opcodes")
+        self.menuedit.add_command(
+                command = lambda: self.open_path("/strs"),
+                label = "Stores")
 
         self.menunav = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menunav,
@@ -839,37 +855,42 @@ class App(tkinter.Frame):
         return self.fmt_hl_rec(self.sim.dlg_idx, "dlgs", grp_id, full, "dlg")
 
     def path_info_outline(self):
-        if self.sim is None:
+        if self.sim is None and self.strfm is None:
             self.add_info("No data loaded. Open PARTS.INI or SCRIPT.DAT first.")
             return
-        self.add_info("Current part {} chapter {}\n\n".\
-                format(self.sim.curr_part, self.sim.curr_chap))
-        self.add_info("  Resources:     <a href=\"/res\">{}</a>\n".\
-            format(len(self.sim.res)))
-        self.add_info("  Objects:       <a href=\"/objs\">{}</a>\n".\
-            format(len(self.sim.objects)))
-        self.add_info("  Scenes:        <a href=\"/scenes\">{}</a>\n".\
-            format(len(self.sim.scenes)))
-        self.add_info("  Names:         <a href=\"/names\">{}</a>\n".\
-            format(len(self.sim.names)))
-        self.add_info("  Invntr:        <a href=\"/invntr\">{}</a>\n".\
-            format(len(self.sim.invntr)))
-        self.add_info("  Casts:         <a href=\"/casts\">{}</a>\n".\
-            format(len(self.sim.casts)))
-        self.add_info("  Messages       <a href=\"/msgs\">{}</a>\n".\
-            format(len(self.sim.msgs)))
-        self.add_info("  Dialog groups: <a href=\"/dlgs\">{}</a>\n".\
-            format(len(self.sim.dlgs)))
-        scn = hlesc(self.sim.start_scene)
-        for scene in self.sim.scenes:
-            if scene.name == self.sim.start_scene:
-                scn = self.fmt_hl_scene(scene.idx, True)
-                break
-        self.add_info("  Start scene:   {}\n".format(scn))
-        self.add_info("\n")
-        self.add_info("  <a href=\"/opcodes\">Opcodes</a>\n")
-        self.add_info("  <a href=\"/dlgops\">Dialog opcodes</a>\n")
-    
+        if self.sim:
+            self.add_info("Current part {} chapter {}\n\n".\
+                    format(self.sim.curr_part, self.sim.curr_chap))
+            self.add_info("  Resources:     <a href=\"/res\">{}</a>\n".\
+                format(len(self.sim.res)))
+            self.add_info("  Objects:       <a href=\"/objs\">{}</a>\n".\
+                format(len(self.sim.objects)))
+            self.add_info("  Scenes:        <a href=\"/scenes\">{}</a>\n".\
+                format(len(self.sim.scenes)))
+            self.add_info("  Names:         <a href=\"/names\">{}</a>\n".\
+                format(len(self.sim.names)))
+            self.add_info("  Invntr:        <a href=\"/invntr\">{}</a>\n".\
+                format(len(self.sim.invntr)))
+            self.add_info("  Casts:         <a href=\"/casts\">{}</a>\n".\
+                format(len(self.sim.casts)))
+            self.add_info("  Messages       <a href=\"/msgs\">{}</a>\n".\
+                format(len(self.sim.msgs)))
+            self.add_info("  Dialog groups: <a href=\"/dlgs\">{}</a>\n".\
+                format(len(self.sim.dlgs)))
+            scn = hlesc(self.sim.start_scene)
+            for scene in self.sim.scenes:
+                if scene.name == self.sim.start_scene:
+                    scn = self.fmt_hl_scene(scene.idx, True)
+                    break
+            self.add_info("  Start scene:   {}\n".format(scn))
+            self.add_info("\n")
+            self.add_info("  <a href=\"/opcodes\">Opcodes</a>\n")
+            self.add_info("  <a href=\"/dlgops\">Dialog opcodes</a>\n\n")
+
+        if self.strfm:
+            self.add_info("  Opened stores: <a href=\"/strs\">{}</a>\n".
+                format(len(self.strfm.strfd)))
+                
 
     def path_default(self, path):
         self.switch_view(0)
@@ -902,6 +923,12 @@ class App(tkinter.Frame):
             ]
             for name, act in acts:
                 self.insert_lb_act(name, act)
+        if self.strfm is not None:
+            acts = [
+                ("Stores ({})".format(len(self.strfm.strfd)), "/strs"),
+            ]
+            for name, act in acts:
+                self.insert_lb_act(name, act)
 
     def path_parts(self, path):
         if self.sim is None:
@@ -1867,6 +1894,39 @@ class App(tkinter.Frame):
                         self.fmt_hl_obj_scene(obj_idx, True))))
                 self.add_info("\n")  
 
+    def path_stores(self, path):
+        if self.strfm is None:
+            return self.path_default([])
+        self.switch_view(0)
+        keys = None
+        if self.last_path[:1] != ("strs",):
+            # calc statistics
+            self.update_gui("Stores ({})".format(len(self.strfm.strfd)))
+            for idx, st in enumerate(self.strfm.strfd):
+                self.insert_lb_act("{}".format(st[1]), ["strs", idx], idx)
+        # change
+        stid = None
+        if len(path) > 1:
+            # index
+            self.select_lb_item(path[1])
+            try:
+                stid = path[1]
+            except:
+                pass
+        else:
+            self.select_lb_item(None)
+        # display
+        self.clear_info()
+        if not stid:
+            self.add_info("<b>Stores</b>\n\n")
+            for idx, st in enumerate(self.strfm.strfd):
+                self.add_info("  {}) <a href=\"/strs/{}\">{}</a> - {}\n".format(
+                    idx + 1, idx, st[1], st[2]))
+        else:
+            self.add_info("<b>Store</b>: {}\n".format(self.strfm.strfd[stid][1]))
+            pass
+            
+        
         
     def path_test(self, path):
         self.update_gui("Test {}".format(path[1]))
@@ -2038,15 +2098,16 @@ class App(tkinter.Frame):
         
     def open_data_from(self, folder):
         self.last_fn = folder
+        self.clear_data()
         try:
-            self.tran = None
             self.sim = petka.Engine()
             self.sim.load_data(folder, "cp1251")
+            self.strfm = self.sim.fman
             self.sim.open_part(0, 0)
             return True
         except:
             print("DEBUG: Error opening")
-            self.sim = None
+            self.clear_data()
             self.switch_view(0)
             self.update_gui("")
             self.clear_info()
@@ -2054,6 +2115,37 @@ class App(tkinter.Frame):
                 format(hlesc(folder), hlesc(traceback.format_exc())))
             #self.clear_hist()
 
+    def on_open_str(self):
+        ft = [\
+            ('STR files', '.STR'),
+            ('all files', '.*')]
+        fn = filedialog.askopenfilename(parent = self, 
+            title = "Open STR file",
+            filetypes = ft,
+            initialdir = os.path.abspath(os.curdir))
+        if not fn: return
+        os.chdir(os.path.dirname(fn))
+        self.clear_hist()
+        if self.open_str_from(fn):
+            self.open_path("")
+            self.clear_hist()
+
+    def open_str_from(self, fn):
+        self.last_fn = fn
+        self.clear_data()
+        try:
+            self.strfm = petka.FileManager(os.path.dirname(fn))
+            self.strfm.load_store(os.path.basename(fn))
+            return True
+        except:
+            print("DEBUG: Error opening")
+            self.clear_data()
+            self.switch_view(0)
+            self.update_gui("")
+            self.clear_info()
+            self.add_info("Error opening \"{}\" \n\n{}".\
+                format(hlesc(fn), hlesc(traceback.format_exc())))
+
     def on_tran_save(self):
         self.on_tran_save_real()
 
@@ -2208,6 +2300,9 @@ def main():
         if argv[0] == "-d": # open data
             app.start_act.append(["load", argv[1]])
             argv = argv[2:]
+        elif argv[0] == "-s": # open str file
+            app.start_act.append(["str", argv[1]])
+            argv = argv[2:]
         elif argv[0] == "-t": # open translation
             app.start_act.append(["tran", argv[1]])
             argv = argv[2:]


Commit: f30896d0193816cb44591854f1c779007465d6c2
    https://github.com/scummvm/scummvm-tools/commit/f30896d0193816cb44591854f1c779007465d6c2
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Store and file view works

Changed paths:
  A engines/petka/help/files.txt
    engines/petka/help/changes.txt
    engines/petka/help/index.txt
    engines/petka/help/list
    engines/petka/p12explore.py
    engines/petka/petka/fman.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index c58820cd0..8d691a52d 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -4,6 +4,9 @@
 ----------------------
 Отображение загруженных хранилищ (.str)
 Загрузка хранилищ из отдельных файлов
+Просмотр списка файлов
+Теперь сообщение об ошибке корректно отображается при открытии через командную 
+  строку
 
 2014-06-10 версия 0.3b
 ----------------------
diff --git a/engines/petka/help/files.txt b/engines/petka/help/files.txt
new file mode 100644
index 000000000..ab776caf5
--- /dev/null
+++ b/engines/petka/help/files.txt
@@ -0,0 +1,5 @@
+Файлы
+
+На этой странице можно просмотреть общий список загруженных файлов.
+
+
diff --git a/engines/petka/help/index.txt b/engines/petka/help/index.txt
index e159406ec..cc3f644f0 100644
--- a/engines/petka/help/index.txt
+++ b/engines/petka/help/index.txt
@@ -18,6 +18,8 @@
  * <a href="/help/msgs">Собщения</a>
  * <a href="/help/dlgs">Группы диалогов</a>
  * <a href="/help/strs">Хранилища</a>
+ * <a href="/help/files">Файлы</a>
+ 
 
 Прочая информация
 
diff --git a/engines/petka/help/list b/engines/petka/help/list
index c8a65be21..332e97473 100644
--- a/engines/petka/help/list
+++ b/engines/petka/help/list
@@ -12,6 +12,7 @@ casts
 msgs
 dlgs
 strs
+files
 translate
 cmdline
 support
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 22211bfa4..fc2afa0b5 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -276,21 +276,31 @@ class App(tkinter.Frame):
         self.path_handler["help"] = self.path_help
         self.path_handler["info"] = self.path_info
         self.path_handler["strs"] = self.path_stores
+        self.path_handler["files"] = self.path_files
         
         self.update_after()
         repath = "/about"
         for cmd, arg in self.start_act:
             if cmd == "load":
-                self.open_data_from(arg)
-                repath = "/"
+                if not self.open_data_from(arg):
+                    repath = ""
+                    break
             elif cmd == "str":
-                self.open_str_from(arg)
-                repath = "/"
+                if not self.open_str_from(arg):
+                    repath = ""
+                    break
+                else:
+                    repath = "/strs"
             elif cmd == "tran":
-                self.open_tran_from(arg)
+                if not self.open_tran_from(arg):
+                    repath = ""
+                    break
             elif cmd == "open":
-                self.open_path(arg)
-                repath = ""
+                if not self.open_path(arg):
+                    repath = ""
+                    break
+                else:
+                    repath = "/"
         if repath:
             self.open_path(repath)
 
@@ -353,6 +363,9 @@ class App(tkinter.Frame):
         self.menuedit.add_command(
                 command = lambda: self.open_path("/strs"),
                 label = "Stores")
+        self.menuedit.add_command(
+                command = lambda: self.open_path("/files"),
+                label = "Files")
 
         self.menunav = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menunav,
@@ -877,6 +890,10 @@ class App(tkinter.Frame):
                 format(len(self.sim.msgs)))
             self.add_info("  Dialog groups: <a href=\"/dlgs\">{}</a>\n".\
                 format(len(self.sim.dlgs)))
+            self.add_info("  Opened stores: <a href=\"/strs\">{}</a>\n".
+                format(len(self.strfm.strfd)))
+            self.add_info("  Files:         <a href=\"/files\">{}</a>\n".
+                format(len(self.strfm.strtable)))
             scn = hlesc(self.sim.start_scene)
             for scene in self.sim.scenes:
                 if scene.name == self.sim.start_scene:
@@ -887,11 +904,13 @@ class App(tkinter.Frame):
             self.add_info("  <a href=\"/opcodes\">Opcodes</a>\n")
             self.add_info("  <a href=\"/dlgops\">Dialog opcodes</a>\n\n")
 
-        if self.strfm:
+        elif self.strfm:
+            self.add_info("Single store mode\n\n")
             self.add_info("  Opened stores: <a href=\"/strs\">{}</a>\n".
                 format(len(self.strfm.strfd)))
+            self.add_info("  Files:         <a href=\"/files\">{}</a>\n".
+                format(len(self.strfm.strtable)))
                 
-
     def path_default(self, path):
         self.switch_view(0)
         self.update_gui("Outline")
@@ -901,7 +920,7 @@ class App(tkinter.Frame):
             for item in path:
                 spath += "/" + str(item)
             self.add_info("Path {} not found\n\n".format(spath))
-        if self.sim is not None:
+        if self.sim or self.strfm:
             self.add_info("Select from <b>outline</b>\n\n")
         self.path_info_outline()
         if self.sim is not None:
@@ -926,6 +945,7 @@ class App(tkinter.Frame):
         if self.strfm is not None:
             acts = [
                 ("Stores ({})".format(len(self.strfm.strfd)), "/strs"),
+                ("Files ({})".format(len(self.strfm.strtable)), "/files"),
             ]
             for name, act in acts:
                 self.insert_lb_act(name, act)
@@ -1917,17 +1937,69 @@ class App(tkinter.Frame):
             self.select_lb_item(None)
         # display
         self.clear_info()
-        if not stid:
+        if stid is None:
             self.add_info("<b>Stores</b>\n\n")
             for idx, st in enumerate(self.strfm.strfd):
                 self.add_info("  {}) <a href=\"/strs/{}\">{}</a> - {}\n".format(
                     idx + 1, idx, st[1], st[2]))
         else:
-            self.add_info("<b>Store</b>: {}\n".format(self.strfm.strfd[stid][1]))
-            pass
+            if stid >= len(self.strfm.strfd):
+                self.add_info("<b>Store</b> \"{}\" not found\n\n".\
+                    format(path[1]))
+                return
+            _, name, tag, strlst = self.strfm.strfd[stid]
+            self.add_info("<b>Store</b>: {}\n".format(name))
+            self.add_info("  Files: <a href=\"/files\">{}</a>, Tag: {}\n\n".
+                format(len(strlst), tag))
+            for idx, (fname, ford, pos, ln) in enumerate(strlst):
+                self.add_info("  {}) <a href=\"/files/{}\">{}</a> "
+                    "(pos={}, len={})\n".format(idx + 1, ford, fname, 
+                    pos, ln))
+
+    def path_files(self, path):
+        if self.strfm is None:
+            return self.path_default([])
+        self.switch_view(0)
+        keys = None
+        if self.last_path[:1] != ("files",):
+            # calc statistics
+            self.update_gui("Files ({})".format(len(self.strfm.strtable)))
+            for idx, fn in enumerate(self.strfm.strtableord):
+                self.insert_lb_act(fn, ["files", idx], idx)
+        # change
+        fid = None
+        if len(path) > 1:
+            # index
+            self.select_lb_item(path[1])
+            try:
+                fid = path[1]
+            except:
+                pass
+        else:
+            self.select_lb_item(None)
+        # display
+        self.clear_info()
+        if fid is None:
+            self.add_info("<b>Files</b>\n\n")
+            for idx, fn in enumerate(self.strfm.strtableord):
+                stid, pos, ln = self.strfm.strtable[fn]
+                self.add_info("  {}) <a href=\"/files/{}\">{}</a>\n".format(
+                    idx + 1, idx, fn))
+        else:
+            if fid >= len(self.strfm.strtable):
+                self.add_info("<b>File</b> \"{}\" not found\n\n".\
+                    format(path[1]))
+                return
+            fn = self.strfm.strtableord[fid]
+            stid, pos, ln = self.strfm.strtable[fn]
+
+            self.add_info("<b>File</b>: {}\n\n".format(fn))
+            self.add_info("  Store: <a href=\"/strs/{}\">{}</a>\n".format(stid,
+                self.strfm.strfd[stid][1]))
+            self.add_info("  Pos: {} (0x{:x})\n  Len: {} (0x{:x})\n".format(
+                pos, pos, ln, ln))
+            
             
-        
-        
     def path_test(self, path):
         self.update_gui("Test {}".format(path[1]))
         self.insert_lb_act("Outline", [])
@@ -1963,11 +2035,11 @@ class App(tkinter.Frame):
         self.clear_info()
         self.add_info("" + APPNAME + " " + VERSION + "\n")
         self.add_info("=" * 40 + "\n")
-        self.add_info("<b>App folder</b>:  {}\n".format(
+        self.add_info("<b>App folder</b>:   {}\n".format(
             hlesc(self.app_path)))
-        self.add_info("<b>Game folder</b>: {}\n".format(
+        self.add_info("<b>Game folder</b>:  {}\n".format(
             hlesc(self.last_fn)))
-        self.add_info("<b>Translation</b>: ")
+        self.add_info("<b>Translation</b>:  ")
         if not polib:
             self.add_info("<i><u>polib</u> not found</i>\n")
         else:
@@ -1976,19 +2048,29 @@ class App(tkinter.Frame):
             else:
                 self.add_info(hlesc(self.tran_fn) + "\n")
                 
-        self.add_info("<b>Engine</b>:      ")
-        if self.sim is None:
+        self.add_info("<b>Engine</b>:       ")
+        if not self.sim:
             self.add_info("<i>not initialized</i>\n")
         else:
             self.add_info("<i>works</i>\n\n")
-            self.add_info("  <b>Path</b>:       {}\n".format(
+            self.add_info("  <b>Path</b>:    {}\n".format(
                 hlesc(self.sim.curr_path)))
-            self.add_info("  <b>Start</b>:      {}.{}\n".format(
+            self.add_info("  <b>Start</b>:   {}.{}\n".format(
                 self.sim.start_part, self.sim.start_chap))
-            self.add_info("  <b>Speech</b>:     {}\n".format(
+            self.add_info("  <b>Speech</b>:  {}\n".format(
                 hlesc(self.sim.curr_speech)))
-            self.add_info("  <b>Disk ID</b>:    {}\n\n".format(
+            self.add_info("  <b>Disk ID</b>: {}\n\n".format(
                 hlesc(self.sim.curr_diskid)))
+
+        self.add_info("<b>File manager</b>: ")
+        if not self.strfm:
+            self.add_info("<i>not initialized</i>\n")
+        else:
+            self.add_info("<i>works</i>\n\n")
+            self.add_info("  <b>Path</b>: {}\n\n".format(
+                hlesc(self.strfm.root)))
+
+        if self.sim or self.strfm:
             self.path_info_outline()
             
     def path_help(self, path):
@@ -2106,14 +2188,13 @@ class App(tkinter.Frame):
             self.sim.open_part(0, 0)
             return True
         except:
-            print("DEBUG: Error opening")
+            print("DEBUG: Error opening data")
             self.clear_data()
             self.switch_view(0)
             self.update_gui("")
             self.clear_info()
             self.add_info("Error opening \"{}\" \n\n{}".\
                 format(hlesc(folder), hlesc(traceback.format_exc())))
-            #self.clear_hist()
 
     def on_open_str(self):
         ft = [\
@@ -2138,13 +2219,14 @@ class App(tkinter.Frame):
             self.strfm.load_store(os.path.basename(fn))
             return True
         except:
-            print("DEBUG: Error opening")
+            print("DEBUG: Error opening STR")
             self.clear_data()
             self.switch_view(0)
             self.update_gui("")
             self.clear_info()
             self.add_info("Error opening \"{}\" \n\n{}".\
                 format(hlesc(fn), hlesc(traceback.format_exc())))
+            
 
     def on_tran_save(self):
         self.on_tran_save_real()
diff --git a/engines/petka/petka/fman.py b/engines/petka/petka/fman.py
index b14ad3eea..427c9a752 100644
--- a/engines/petka/petka/fman.py
+++ b/engines/petka/petka/fman.py
@@ -15,7 +15,8 @@ class FileManager:
     
         self.strfd = []
         self.strtable = {}
-    
+        self.strtableord = []
+   
     def find_path(self, path):
         # search case insensive from root
         dpath = []
@@ -56,17 +57,20 @@ class FileManager:
             temp = f.read(12) 
             data = struct.unpack_from("<III", temp)
             index_table.append((data[1], data[2]))
+        strlst = []
         data = f.read().decode("latin-1")
         for idx, fname in enumerate(data.split("\x00")):
             fname = fname.lower().replace("\\", "/")
             if idx < index_len and fname not in self.strtable:
                 self.strtable[fname] = (len(self.strfd),) + index_table[idx]
+                strlst.append((fname, len(self.strtableord)) + index_table[idx])
+                self.strtableord.append(fname)
             else:
                 if len(fname) > 0:
                     print("DEBUG:Extra file record \"{}\" in \"{}\"".\
                         format(fname, name))
         # add file descriptor
-        self.strfd.append((f, name, tag))
+        self.strfd.append((f, name, tag, strlst))
         print("DEBUG: Loaded store \"{}\"".format(name))
         
     def read_file(self, fname):
@@ -107,13 +111,15 @@ class FileManager:
     def unload_stores(self, flt = None):
         strfd = []
         strtable = {}
-        for idx, (fd, name, tag) in enumerate(self.strfd):
+        strtableord = []
+        for idx, (fd, name, tag, strlst) in enumerate(self.strfd):
             if flt is not None:
                 if tag != flt:
                     for k, v in self.strtable.items():
                         if v[0] == idx:
                             strtable[k] = (len(strfd), v[1], v[2])
-                    strfd.append((fd, name, tag))
+                            strtableord.append(k)
+                    strfd.append((fd, name, tag, strlst))
                     continue
             print("DEBUG: Unload store \"{}\"".format(name))
             try:
@@ -122,4 +128,6 @@ class FileManager:
                 print("DEBUG: Can't unload \"{}\":".format(name) + str(e))
         self.strfd = strfd
         self.strtable = strtable
+        self.strtableord = strtableord
+        
         


Commit: 97f1822092773fdee2b4f8a4ad100c5815eff9bc
    https://github.com/scummvm/scummvm-tools/commit/97f1822092773fdee2b4f8a4ad100c5815eff9bc
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Display sorted wav list in messages section

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 8d691a52d..4e70b999d 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,5 +1,9 @@
 Что нового
 ==========
+2014-12-20 версия 0.3d
+----------------------
+Отображение отсортированного списка используемых wav файлов
+
 2014-12-20 версия 0.3c
 ----------------------
 Отображение загруженных хранилищ (.str)
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index fc2afa0b5..596973e46 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -28,7 +28,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.3c 2014-12-20"
+VERSION = "v0.3d 2014-12-20"
 
 def hlesc(value):
     if value is None:
@@ -1539,6 +1539,15 @@ class App(tkinter.Frame):
                 self.add_info("<b>Message</b> \"{}\" not found\n\n".format(
                     path[1]))
             self.add_info("Select <b>message</b> from list\n")
+            # wav
+            lst = []
+            for idx, msg in enumerate(self.sim.msgs):
+                lst.append((msg.msg_wav, idx, msg.name))
+            lst.sort()
+            self.add_info("\nWav files\n")
+            for wav, idx, capt in lst:
+                self.add_info("  <a href=\"/msgs/{}\">{}</a> - {}\n".format(
+                    idx, wav, capt))
         else:
             # msg info
             self.add_info("<b>Message</b>: {}\n".format(path[1]))


Commit: aa4025e708b1ac6faed5aa5eae1cbba88bab9aeb
    https://github.com/scummvm/scummvm-tools/commit/aa4025e708b1ac6faed5aa5eae1cbba88bab9aeb
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Extract STR files

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/help/index.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 4e70b999d..a2c11002b 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,5 +1,9 @@
 Что нового
 ==========
+2014-12-20 версия 0.3e
+----------------------
+Распаковка содержимого STR файла
+
 2014-12-20 версия 0.3d
 ----------------------
 Отображение отсортированного списка используемых wav файлов
diff --git a/engines/petka/help/index.txt b/engines/petka/help/index.txt
index cc3f644f0..9ba1946ba 100644
--- a/engines/petka/help/index.txt
+++ b/engines/petka/help/index.txt
@@ -1,12 +1,22 @@
 Справка
 
-Версия: 0.3c 2014-12-20
+Версия: 0.3e 2014-12-20
 
+Этот набор программ предназначен для просмотра внутренних структур двух первых 
+частей игры "Петька и Василий Иванович", включая демо-версию.
+
+Возможности:
+
+ * Открывать и отображать в удобной форме внутреннюю структуру игры 
+ * Просматривать и распаковывать содержимое .STR файлов
+ * Работать с переводом игры на другой язык (см. <a href="/help/translate">для переводчика</a>)
+ * Декомпилировать структуру и <a href="/help/compiler">компилировать</a> обратно
  * <a href="/help/changes">Что нового</a>
  * <a href="/help/faq">Часто задаваемые вопросы</a>
- * <a href="/help/license">Лицензии</a>
 
-Отображаемые разделы
+Информация о <a href="/help/license">лицензиях</a>
+
+Отображаемые разделы:
 
  * <a href="/help/parts">Выбор части</a>
  * <a href="/help/res">Ресурсы</a>
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 596973e46..edc091454 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -28,7 +28,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.3d 2014-12-20"
+VERSION = "v0.3e 2014-12-20"
 
 def hlesc(value):
     if value is None:
@@ -1933,6 +1933,45 @@ class App(tkinter.Frame):
             self.update_gui("Stores ({})".format(len(self.strfm.strfd)))
             for idx, st in enumerate(self.strfm.strfd):
                 self.insert_lb_act("{}".format(st[1]), ["strs", idx], idx)
+            def ext_str():
+                stid = None
+                try:
+                    print(self.curr_path)
+                    stid = self.curr_path[1]
+                except:
+                    pass
+                if stid is None:
+                    self.clear_info()
+                    self.add_info("<b>Select store</b>\n")
+                    return
+                # extract to folder
+                sdir = filedialog.askdirectory(parent = self,
+                    title = "Select folder for extract", 
+                    initialdir = self.strfm.root, mustexist = True)
+                if not sdir: return
+                # extract store to sdir
+                fd, _, _, strlst = self.strfm.strfd[stid]
+                self.clear_info()
+                for fname, _, pos, ln in strlst:
+                    try:
+                        self.add_info("  \"{}\" - {} bytes\n".
+                            format(hlesc(fname), ln))
+                        np = sdir
+                        for elem in fname.split("/"):
+                            np = os.path.join(np, elem)
+                        # check folder
+                        bn = os.path.dirname(np)
+                        if not os.path.exists(bn):
+                            os.makedirs(bn)
+                        f = open(np, "wb")
+                        fd.seek(pos)
+                        f.write(fd.read(ln))
+                        f.close()
+                    except:
+                        self.add_info("Error extracting \"{}\" \n\n{}".\
+                            format(hlesc(fname), hlesc(traceback.format_exc())))
+                        return
+            self.add_toolbtn("Extract STR", ext_str)
         # change
         stid = None
         if len(path) > 1:


Commit: 6c2a6061aec439fcdfcd0f707e899f1e3728a366
    https://github.com/scummvm/scummvm-tools/commit/6c2a6061aec439fcdfcd0f707e899f1e3728a366
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: testing page modes

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index edc091454..c12d9f1eb 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -173,9 +173,8 @@ class App(tkinter.Frame):
         self.curr_help = ""
         self.last_path = [None]
         self.last_fn = ""
-        self.curr_mode = 0
-        self.curr_mode_sub = None
         self.curr_gui = []
+        self.curr_state = {}
         self.curr_lb_acts = None
         self.curr_lb_idx = None
         self.hist = []
@@ -597,6 +596,7 @@ class App(tkinter.Frame):
         for item in self.curr_gui:
             item()
         self.curr_gui = []
+        self.curr_state = {} # save state across moves
         # left listbox
         lab = tkinter.Label(self.frm_left, text = text)
         lab.pack()
@@ -768,7 +768,9 @@ class App(tkinter.Frame):
         btn = ttk.Button(self.toolbar, text = text, \
             style = "Tool.TButton", command = cmd)
         btn.pack(side = tkinter.LEFT)
-        self.curr_gui.append(lambda:btn.pack_forget())    
+        self.curr_gui.append(lambda:btn.pack_forget())
+        return btn
+        
 
     def clear_hist(self):
         self.hist = self.hist[-1:]
@@ -1936,7 +1938,6 @@ class App(tkinter.Frame):
             def ext_str():
                 stid = None
                 try:
-                    print(self.curr_path)
                     stid = self.curr_path[1]
                 except:
                     pass
@@ -1968,7 +1969,7 @@ class App(tkinter.Frame):
                         f.write(fd.read(ln))
                         f.close()
                     except:
-                        self.add_info("Error extracting \"{}\" \n\n{}".\
+                        self.add_info("Error extracting \"{}\"\n\n{}".\
                             format(hlesc(fname), hlesc(traceback.format_exc())))
                         return
             self.add_toolbtn("Extract STR", ext_str)
@@ -2049,20 +2050,63 @@ class App(tkinter.Frame):
             
             
     def path_test(self, path):
-        self.update_gui("Test {}".format(path[1]))
-        self.insert_lb_act("Outline", [])
-        self.insert_lb_act("-", None)
-        for i in range(15):
-            self.insert_lb_act("{} #{}".format(path[1], i), path[:2] + (i,))
-        if path[1] == "image":
-            self.switch_view(1)
-            self.main_image = tkinter.PhotoImage(file = "img/splash.gif")
-        elif path[1] == "info":
-            self.switch_view(0)
+        def display_page():
+            item = None
+            path = self.curr_path
+            if len(path) > 2:
+                item = path[2]
             self.clear_info()
-            self.add_info("Information panel for {}\n".format(path))
-            for i in range(100):
-                self.add_info("  Item {}\n".format(i))
+            if item is None:
+                self.switch_view(0)
+                self.add_info("Select item " + path[1])
+            else:
+                if path[1] == "image":
+                    self.switch_view(1)
+                    self.main_image = tkinter.PhotoImage(file = "img/splash.gif")
+                elif path[1] == "info":
+                    self.switch_view(0)
+                    self.add_info("Information panel for {}\n".format(path))
+                    self.add_info("Current mode {}\n".format(
+                        self.curr_state.get("mode", None)))
+                    for i in range(100):
+                        self.add_info("  Item {}\n".format(i))
+            
+        if self.last_path[:1] != ("test",):
+            self.update_gui("Test {}".format(path[1]))
+            self.insert_lb_act("Outline", [])
+            self.insert_lb_act("-", None)
+            for i in range(15):
+                self.insert_lb_act("{} #{}".format(path[1], i), 
+                    path[:2] + (i,), i)
+            # create mode buttons
+            def sw_mode1():
+                print("Mode 1")
+                self.curr_state["mode"] = 1
+                self.curr_state["btn1"].config(state = tkinter.DISABLED)
+                self.curr_state["btn2"].config(state = tkinter.NORMAL)
+                display_page()
+            def sw_mode2():
+                print("Mode 2")
+                self.curr_state["mode"] = 2
+                self.curr_state["btn1"].config(state = tkinter.NORMAL)
+                self.curr_state["btn2"].config(state = tkinter.DISABLED)
+                display_page()
+            self.curr_state["btn1"] = self.add_toolbtn("Mode 1", sw_mode1)
+            self.curr_state["btn2"] = self.add_toolbtn("Mode 2", sw_mode2)
+
+        # change
+        item = None
+        if len(path) > 2:
+            # index
+            self.select_lb_item(path[2])
+            try:
+                item = path[2]
+            except:
+                pass
+        else:
+            self.select_lb_item(None)
+        # display
+        display_page()
 
     def path_about(self, path):
         self.switch_view(0)


Commit: 0add1b8e92bacb7ba8d06297c000919c7e108097
    https://github.com/scummvm/scummvm-tools/commit/0add1b8e92bacb7ba8d06297c000919c7e108097
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: show history path

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index c12d9f1eb..8af849dd1 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -174,11 +174,12 @@ class App(tkinter.Frame):
         self.last_path = [None]
         self.last_fn = ""
         self.curr_gui = []
-        self.curr_state = {}
+        self.curr_state = {} # local state for location group
         self.curr_lb_acts = None
         self.curr_lb_idx = None
         self.hist = []
         self.histf = []
+        self.gl_state = {} # global state until program exit
         # canvas
         self.need_update = False
         self.canv_view_fact = 1
@@ -381,8 +382,7 @@ class App(tkinter.Frame):
                 label = "Outline")
         self.menunav.add_separator()
         self.menunav.add_command(
-                command = lambda: self.open_path("/hist"),
-                label = "History")
+                command = self.show_hist, label = "History")
 
         if polib:
             menutran = tkinter.Menu(self.menubar, tearoff = 0)
@@ -912,6 +912,26 @@ class App(tkinter.Frame):
                 format(len(self.strfm.strfd)))
             self.add_info("  Files:         <a href=\"/files\">{}</a>\n".
                 format(len(self.strfm.strtable)))
+
+    def show_hist(self):
+        self.switch_view(0)
+        self.clear_info()
+        self.add_info("<b>History</b>\n\n")
+        def phist(h):
+            d = ""
+            for e in h:
+                d += "/{}".format(e)
+            return d
+        for idx, h in enumerate(self.hist[:-1]):
+            self.add_info(" {:5d}) {}\n".format(idx - len(self.hist) + 1, 
+                phist(h[0])))
+        self.add_info(" -----> {}\n".format(phist(self.curr_path)))
+        
+        for idx, h in enumerate(self.histf):
+            self.add_info(" {:5d}) {}\n".format(idx + 1, phist(h[0])))
+            
+        
+        
                 
     def path_default(self, path):
         self.switch_view(0)
@@ -2066,8 +2086,10 @@ class App(tkinter.Frame):
                 elif path[1] == "info":
                     self.switch_view(0)
                     self.add_info("Information panel for {}\n".format(path))
-                    self.add_info("Current mode {}\n".format(
+                    self.add_info("Local mode {}\n".format(
                         self.curr_state.get("mode", None)))
+                    self.add_info("Global mode {}\n".format(
+                        self.gl_state.get("mode", None)))
                     for i in range(100):
                         self.add_info("  Item {}\n".format(i))
             
@@ -2091,8 +2113,30 @@ class App(tkinter.Frame):
                 self.curr_state["btn1"].config(state = tkinter.NORMAL)
                 self.curr_state["btn2"].config(state = tkinter.DISABLED)
                 display_page()
+            def sw_gmode1():
+                print("Global Mode 1")
+                self.gl_state["test.info.mode"] = 0
+                self.curr_state["gbtn1"].config(state = tkinter.DISABLED)
+                self.curr_state["gbtn2"].config(state = tkinter.NORMAL)
+                display_page()
+            def sw_gmode2():
+                print("Global Mode 2")
+                self.gl_state["test.info.mode"] = 1
+                self.curr_state["gbtn1"].config(state = tkinter.NORMAL)
+                self.curr_state["gbtn2"].config(state = tkinter.DISABLED)
+                display_page()
             self.curr_state["btn1"] = self.add_toolbtn("Mode 1", sw_mode1)
             self.curr_state["btn2"] = self.add_toolbtn("Mode 2", sw_mode2)
+            # we store buttons in local state
+            st = self.gl_state.get("test.info.mode", 0)
+            b = self.add_toolbtn("Global Mode 1", sw_gmode1)
+            if st == 0:
+                b.config(state = tkinter.DISABLED)
+            self.curr_state["gbtn1"] = b
+            b = self.add_toolbtn("Global Mode 2", sw_gmode2)
+            if st == 1:
+                b.config(state = tkinter.DISABLED)
+            self.curr_state["gbtn2"] = b
 
         # change
         item = None


Commit: 9128fae7fa23fbc401be0fb02dfcf53f6f9c7c18
    https://github.com/scummvm/scummvm-tools/commit/9128fae7fa23fbc401be0fb02dfcf53f6f9c7c18
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: sort modes for messages

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 8af849dd1..a0e418574 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1533,6 +1533,71 @@ class App(tkinter.Frame):
         if self.sim is None:
             return self.path_default([])
         self.switch_view(0)
+        def upd_msgs():
+            path = self.curr_path
+            msg = None
+            if len(path) > 1:
+                # index
+                self.select_lb_item(path[1])
+                try:
+                    msg = self.sim.msgs[path[1]]
+                except:
+                    pass
+            else:
+                self.select_lb_item(None)
+            # display
+            sm = self.gl_state.get("msgs.sort", 0)
+            if msg:
+                sm = -1
+            for btn, idx in self.curr_state["btnsort"]:
+                if idx != sm and sm != -1:
+                    btn.config(state = tkinter.NORMAL)
+                else:
+                    btn.config(state = tkinter.DISABLED)
+            self.clear_info()
+            if not msg:
+                if len(path) > 1:
+                    self.add_info("<b>Message</b> \"{}\" not found\n\n".format(
+                        path[1]))
+                self.add_info("Select <b>message</b> from list\n\n")
+                # wav
+                lst = []
+                for idx, msg in enumerate(self.sim.msgs):
+                    if sm == 0:
+                        k = msg.msg_wav
+                    elif sm == 1:
+                        k = idx
+                    else:
+                        k = msg.name
+                    lst.append((k, msg.msg_wav, idx, msg.name))
+                lst.sort()
+                for _, wav, idx, capt in lst:
+                    self.add_info("  <a href=\"/msgs/{}\">{}</a> - {}\n".
+                        format(idx, wav, capt))
+            else:
+                # msg info
+                self.add_info("<b>Message</b>: {}\n".format(path[1]))
+                self.add_info("  wav:    {}\n".format(msg.msg_wav))
+                self.add_info("  object: " + self.fmt_hl_obj(msg.obj.idx, 
+                    True) + "\n")
+                self.add_info("  arg2:   {a} (0x{a:X})\n".format(
+                    a = msg.msg_arg2))
+                self.add_info("  arg3:   {a} (0x{a:X})\n".format(
+                    a = msg.msg_arg3))
+                self.add_info("\n{}\n".format(hlesc(msg.name)))
+                if self.tran:
+                    self.add_info("\n<i>Translated:</i>\n{}\n".\
+                        format(hlesc(self._t(msg.name, "msg"))))
+                self.add_info("\n<b>Used by dialog groups</b>:\n")
+                for grp in self.sim.dlgs:
+                    for act in grp.acts:
+                        for dlg in act.dlgs:
+                            for op in dlg.ops:
+                                if not op.msg: continue
+                                if op.msg.idx == msg.idx and op.opcode == 7:
+                                    self.add_info("  " + 
+                                        self.fmt_hl_dlg(grp.idx, True) + "\n")
+        
         if self.last_path[:1] != ("msgs",):
             self.update_gui("Messages ({})".format(len(self.sim.msgs)))
             for idx, msg in enumerate(self.sim.msgs):
@@ -1543,54 +1608,21 @@ class App(tkinter.Frame):
                     capt = capt[:40] + "|"
                 self.insert_lb_act("{} - {}".format(msg.idx, capt),
                     ["msgs", idx], idx)
+            def sfn():
+                self.gl_state["msgs.sort"] = 0
+                upd_msgs()
+            def sidx():
+                self.gl_state["msgs.sort"] = 1
+                upd_msgs()
+            def stxt():
+                self.gl_state["msgs.sort"] = 2
+                upd_msgs()
+            b1 = self.add_toolbtn("Sort by wav", sfn)
+            b2 = self.add_toolbtn("Sort by order", sidx)
+            b3 = self.add_toolbtn("Sort by text", stxt)
+            self.curr_state["btnsort"] = [[b1, 0], [b2, 1], [b3, 2]]
         # change
-        msg = None
-        if len(path) > 1:
-            # index
-            self.select_lb_item(path[1])
-            try:
-                msg = self.sim.msgs[path[1]]
-            except:
-                pass
-        else:
-            self.select_lb_item(None)
-        # display
-        self.clear_info()
-        if not msg:
-            if len(path) > 1:
-                self.add_info("<b>Message</b> \"{}\" not found\n\n".format(
-                    path[1]))
-            self.add_info("Select <b>message</b> from list\n")
-            # wav
-            lst = []
-            for idx, msg in enumerate(self.sim.msgs):
-                lst.append((msg.msg_wav, idx, msg.name))
-            lst.sort()
-            self.add_info("\nWav files\n")
-            for wav, idx, capt in lst:
-                self.add_info("  <a href=\"/msgs/{}\">{}</a> - {}\n".format(
-                    idx, wav, capt))
-        else:
-            # msg info
-            self.add_info("<b>Message</b>: {}\n".format(path[1]))
-            self.add_info("  wav:    {}\n".format(msg.msg_wav))
-            self.add_info("  object: " + self.fmt_hl_obj(msg.obj.idx, True) + 
-                "\n")
-            self.add_info("  arg2:   {a} (0x{a:X})\n".format(a = msg.msg_arg2))
-            self.add_info("  arg3:   {a} (0x{a:X})\n".format(a = msg.msg_arg3))
-            self.add_info("\n{}\n".format(hlesc(msg.name)))
-            if self.tran:
-                self.add_info("\n<i>Translated:</i>\n{}\n".\
-                    format(hlesc(self._t(msg.name, "msg"))))
-            self.add_info("\n<b>Used by dialog groups</b>:\n")
-            for grp in self.sim.dlgs:
-                for act in grp.acts:
-                    for dlg in act.dlgs:
-                        for op in dlg.ops:
-                            if not op.msg: continue
-                            if op.msg.idx == msg.idx and op.opcode == 7:
-                                self.add_info("  " + 
-                                    self.fmt_hl_dlg(grp.idx, True) + "\n")
+        upd_msgs()
                 
     def path_dlgs(self, path):
         if self.sim is None:
@@ -1992,7 +2024,7 @@ class App(tkinter.Frame):
                         self.add_info("Error extracting \"{}\"\n\n{}".\
                             format(hlesc(fname), hlesc(traceback.format_exc())))
                         return
-            self.add_toolbtn("Extract STR", ext_str)
+            self.curr_state["btnext"] = self.add_toolbtn("Extract STR", ext_str)
         # change
         stid = None
         if len(path) > 1:
@@ -2006,6 +2038,7 @@ class App(tkinter.Frame):
             self.select_lb_item(None)
         # display
         self.clear_info()
+        self.curr_state["btnext"].config(state = tkinter.DISABLED)
         if stid is None:
             self.add_info("<b>Stores</b>\n\n")
             for idx, st in enumerate(self.strfm.strfd):
@@ -2016,6 +2049,7 @@ class App(tkinter.Frame):
                 self.add_info("<b>Store</b> \"{}\" not found\n\n".\
                     format(path[1]))
                 return
+            self.curr_state["btnext"].config(state = tkinter.NORMAL)
             _, name, tag, strlst = self.strfm.strfd[stid]
             self.add_info("<b>Store</b>: {}\n".format(name))
             self.add_info("  Files: <a href=\"/files\">{}</a>, Tag: {}\n\n".


Commit: 1e024eabb3128f52b869209b56dc729691caeef8
    https://github.com/scummvm/scummvm-tools/commit/1e024eabb3128f52b869209b56dc729691caeef8
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: add label to toolbar

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index a0e418574..b7ef4362f 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -771,6 +771,11 @@ class App(tkinter.Frame):
         self.curr_gui.append(lambda:btn.pack_forget())
         return btn
         
+    def add_toollabel(self, text):
+        lab = ttk.Label(self.toolbar, text = text)
+        lab.pack(side = tkinter.LEFT)
+        self.curr_gui.append(lambda:lab.pack_forget())
+        return lab
 
     def clear_hist(self):
         self.hist = self.hist[-1:]
@@ -1617,13 +1622,14 @@ class App(tkinter.Frame):
             def stxt():
                 self.gl_state["msgs.sort"] = 2
                 upd_msgs()
-            b1 = self.add_toolbtn("Sort by wav", sfn)
-            b2 = self.add_toolbtn("Sort by order", sidx)
-            b3 = self.add_toolbtn("Sort by text", stxt)
+            self.add_toollabel("Sort by")
+            b1 = self.add_toolbtn("wav", sfn)
+            b2 = self.add_toolbtn("order", sidx)
+            b3 = self.add_toolbtn("text", stxt)
             self.curr_state["btnsort"] = [[b1, 0], [b2, 1], [b3, 2]]
         # change
         upd_msgs()
-                
+
     def path_dlgs(self, path):
         if self.sim is None:
             return self.path_default([])
@@ -1731,7 +1737,7 @@ class App(tkinter.Frame):
                             op.opcode == 0x4: # GOTO or MENURET
                             opref = "<i>label_{:X}</i>".format(op.ref)
                             if op.pos in usedmenu:
-                                cmt = self.fmt_cmt(" // action menu=<i>"\
+                                cmt = self.fmt_cmt(" // action menu=<i> "\
                                     "label_{:X}</i>, case=0x{:}".\
                                     format(*usedmenu[op.pos]))
                         elif op.opcode == 0x7:


Commit: d87373a2bfd5792d940dd35447440c250045c35a
    https://github.com/scummvm/scummvm-tools/commit/d87373a2bfd5792d940dd35447440c250045c35a
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: files related info

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index b7ef4362f..c3c5c4009 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -213,7 +213,8 @@ class App(tkinter.Frame):
         ]
         for text, cmd in btns:
             if text is None:
-                frm = ttk.Frame(self.toolbar, width = self.pad, height = self.pad)
+                frm = ttk.Frame(self.toolbar, width = self.pad, 
+                    height = self.pad)
                 frm.pack(side = tkinter.LEFT)
                 continue            
             btn = ttk.Button(self.toolbar, text = text, \
@@ -2068,6 +2069,85 @@ class App(tkinter.Frame):
     def path_files(self, path):
         if self.strfm is None:
             return self.path_default([])
+        def upd_files():
+            path = self.curr_path
+            fid = None
+            if len(path) > 1:
+                # index
+                self.select_lb_item(path[1])
+                try:
+                    fid = path[1]
+                except:
+                    pass
+            else:
+                self.select_lb_item(None)
+            self.clear_info()
+            if fid is None:
+                self.add_info("<b>Files</b>\n\n")
+                for idx, fn in enumerate(self.strfm.strtableord):
+                    stid, pos, ln = self.strfm.strtable[fn]
+                    self.add_info("  {}) <a href=\"/files/{}\">{}</a>\n".format(
+                        idx + 1, idx, hlesc(fn)))
+            else:
+                if fid >= len(self.strfm.strtable):
+                    self.add_info("<b>File</b> \"{}\" not found\n\n".\
+                        format(path[1]))
+                    return
+                fn = self.strfm.strtableord[fid]
+                stid, pos, ln = self.strfm.strtable[fn]
+
+                self.add_info("<b>File</b>: {}\n\n".format(fn))
+                self.add_info("  Store: <a href=\"/strs/{}\">{}</a>\n".format(
+                    stid, self.strfm.strfd[stid][1]))
+                self.add_info("  Pos: {} (0x{:x})\n  Len: {} (0x{:x})\n".format(
+                    pos, pos, ln, ln))
+                fnl = fn.lower().replace("\\", "/")
+                if self.sim:
+                    self.add_info("\n<b>Used in resources</b>:\n")
+                    for resid in self.sim.resord:
+                        resfn = self.sim.res[resid].lower().replace("\\", "/")
+                        if resfn == fnl:
+                            self.add_info("  {} - <a href=\"/res/all/{}\">"\
+                                "{}</a>\n".format(resid, resid, 
+                                hlesc(self.sim.res[resid])))
+                grp = [
+                    [".leg", ".off", ".msk", ".flc"],
+                    [".bmp", "cvx"]
+                ]
+                sg = None
+                for g in grp:
+                    if fnl[-4:] in g:
+                        sg = g
+                if sg:
+                    self.add_info("\n<b>Related file</b>:\n")
+                    rel = []
+                    for idx, fnm in enumerate(self.strfm.strtableord):
+                        fnml = fnm.lower().replace("\\", "/")
+                        if idx == fid: continue
+                        if fnml[:-4] == fnl[:-4] and fnml[-4:] in sg:
+                            self.add_info("  <a href=\"/files/{}\">{}</a>\n".
+                                format(idx, hlesc(fnm)))
+                # spec files
+                sfils = {
+                    "script.dat": ["/objs", "/scenes", "/opcodes"], 
+                    "background.bg": ["/objs", "/scenes", "/opcodes"], 
+                    "cast.ini": ["/casts"],
+                    "invntr.txt": ["/invntr"],
+                    "names.ini": ["/names"],
+                    "bgs.ini": ["/bgs"],
+                    "dialogue.fix": ["/dlgs", "/msgs", "/dlgops"],
+                    "dialogue.lod": ["/dlgs", "/msgs", "/dlgops"],
+                    "resource.qrc": ["/res"],
+                }
+                for k, v in sfils.items():
+                    if fnl.endswith(k):
+                        self.add_info("\n<b>Related info</b>:\n")
+                        for p in v:
+                            self.add_info("  {}\n".format(p))
+                        
+                
+                    
+            
         self.switch_view(0)
         keys = None
         if self.last_path[:1] != ("files",):
@@ -2075,38 +2155,7 @@ class App(tkinter.Frame):
             self.update_gui("Files ({})".format(len(self.strfm.strtable)))
             for idx, fn in enumerate(self.strfm.strtableord):
                 self.insert_lb_act(fn, ["files", idx], idx)
-        # change
-        fid = None
-        if len(path) > 1:
-            # index
-            self.select_lb_item(path[1])
-            try:
-                fid = path[1]
-            except:
-                pass
-        else:
-            self.select_lb_item(None)
-        # display
-        self.clear_info()
-        if fid is None:
-            self.add_info("<b>Files</b>\n\n")
-            for idx, fn in enumerate(self.strfm.strtableord):
-                stid, pos, ln = self.strfm.strtable[fn]
-                self.add_info("  {}) <a href=\"/files/{}\">{}</a>\n".format(
-                    idx + 1, idx, fn))
-        else:
-            if fid >= len(self.strfm.strtable):
-                self.add_info("<b>File</b> \"{}\" not found\n\n".\
-                    format(path[1]))
-                return
-            fn = self.strfm.strtableord[fid]
-            stid, pos, ln = self.strfm.strtable[fn]
-
-            self.add_info("<b>File</b>: {}\n\n".format(fn))
-            self.add_info("  Store: <a href=\"/strs/{}\">{}</a>\n".format(stid,
-                self.strfm.strfd[stid][1]))
-            self.add_info("  Pos: {} (0x{:x})\n  Len: {} (0x{:x})\n".format(
-                pos, pos, ln, ln))
+        upd_files()
             
             
     def path_test(self, path):


Commit: 98b6710ceb85856e90e1af6ac911a9e420bf7cbc
    https://github.com/scummvm/scummvm-tools/commit/98b6710ceb85856e90e1af6ac911a9e420bf7cbc
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fix bunch of error. improvements

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index a2c11002b..f74e2cb16 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,5 +1,18 @@
 Что нового
 ==========
+2014-12-27 версия 0.3f
+----------------------
+Сортировка сообщений по имени файла, по порядку, по тексту
+Информация о связанных файлах (FLC, MSG, LEF, OFF и BMP, CVX)
+Исправлена ошибка при переходе из хранилища к файлу
+Добавлен переход от ресурса к файлу
+Открытие файлов осуществляется по имени а не номеру
+Отображение истории переходов
+Корректное описание текущего раздела
+Изменено отображение списка сообщений, добавлен переход на wav файл
+Движок: автоматическая загрузка файлов speech из хранилищ
+Исправлена последовательная загрузка разделов
+
 2014-12-20 версия 0.3e
 ----------------------
 Распаковка содержимого STR файла
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index c3c5c4009..ee372fb6d 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -8,6 +8,7 @@ import tkinter
 from tkinter import ttk, font, filedialog, messagebox
 from idlelib.WidgetRedirector import WidgetRedirector
 import traceback
+import urllib.parse
 
 # Image processing
 try:
@@ -192,8 +193,7 @@ class App(tkinter.Frame):
         # store manager
         self.strfm = None
         # translation
-        self.tran = None
-    
+        self.tran = None   
         
     def create_widgets(self):
         ttk.Style().configure("Tool.TButton", width = -1) # minimal width
@@ -259,28 +259,51 @@ class App(tkinter.Frame):
         self.text_hl = HyperlinkManager(self.text_view)
         self.text_view.bind('<Configure>', self.on_resize_view)
         
-        # bind path handlers
-        self.path_handler["parts"] = self.path_parts
-        self.path_handler["res"] = self.path_res
-        self.path_handler["objs"] = self.path_objs_scenes
-        self.path_handler["scenes"] = self.path_objs_scenes
-        self.path_handler["names"] = self.path_names
-        self.path_handler["invntr"] = self.path_invntr
-        self.path_handler["msgs"] = self.path_msgs
-        self.path_handler["dlgs"] = self.path_dlgs
-        self.path_handler["casts"] = self.path_casts
-        self.path_handler["opcodes"] = self.path_opcodes
-        self.path_handler["dlgops"] = self.path_dlgops
-        self.path_handler["test"] = self.path_test
-        self.path_handler["about"] = self.path_about
-        self.path_handler["support"] = self.path_support
-        self.path_handler["help"] = self.path_help
-        self.path_handler["info"] = self.path_info
-        self.path_handler["strs"] = self.path_stores
-        self.path_handler["files"] = self.path_files
         
+        def desc_def(aname, name, lst = {}):
+            def desc(path):
+                if len(path) > 1:
+                    return "{} {}".format(name, lst.get(path[1], 
+                        "#{}".format(path[1])))
+                else:
+                    return aname
+            return desc
+        
+        # bind path handlers
+        self.path_handler["parts"] = [self.path_parts, self.desc_parts]
+        self.path_handler["res"] = [self.path_res, None] # TODO:
+        self.path_handler["objs"] = [self.path_objs_scenes,
+            desc_def("Objects", "Object")]
+        self.path_handler["scenes"] = [self.path_objs_scenes,
+            desc_def("Scenes", "Scene")]
+        self.path_handler["names"] = [self.path_names,
+            desc_def("Names", "Name")]
+        self.path_handler["invntr"] = [self.path_invntr,
+            desc_def("Invntrs", "Invntr")]
+        self.path_handler["msgs"] = [self.path_msgs,
+            desc_def("Messages", "Message")]
+        self.path_handler["dlgs"] = [self.path_dlgs,
+            desc_def("Dialog groups", "Dialog group")]
+        self.path_handler["casts"] = [self.path_casts,
+            desc_def("Casts", "Cast")]
+        self.path_handler["opcodes"] = [self.path_opcodes,
+            desc_def("Opcodes", "Opcode", 
+            {k:v[0] for k, v in petka.engine.OPCODES.items()})]
+        self.path_handler["dlgops"] = [self.path_dlgops,
+            desc_def("Dialog opcodes", "Dialog opcode",
+            {k:v[0] for k, v in petka.engine.DLGOPS.items()})]
+        self.path_handler["strs"] = [self.path_stores,
+            desc_def("Stores", "Store")]
+        self.path_handler["files"] = [self.path_files, self.desc_files]
+        self.path_handler["test"] = [self.path_test, "Tests"]
+        self.path_handler["about"] = [self.path_about, "About"]
+        self.path_handler["support"] = [self.path_support, "Support"]
+        self.path_handler["help"] = [self.path_help, self.desc_help]
+        self.path_handler["info"] = [self.path_info, "Information"]
+
         self.update_after()
         repath = "/about"
+        print(self.start_act)
         for cmd, arg in self.start_act:
             if cmd == "load":
                 if not self.open_data_from(arg):
@@ -297,15 +320,27 @@ class App(tkinter.Frame):
                     repath = ""
                     break
             elif cmd == "open":
+                repath = ""
                 if not self.open_path(arg):
+                    print("DEBUG: stop opening after " + arg)
                     repath = ""
                     break
-                else:
-                    repath = "/"
+        print("Loading end", repath)
         if repath:
             self.open_path(repath)
 
     def create_menu(self):
+        def mkmenupaths(parent, items):                
+            for n in items:
+                if n is None:
+                    parent.add_separator()
+                else:
+                    def cmd(n):
+                        return lambda: self.open_path(n)
+                    parent.add_command(
+                            command = cmd(n),
+                            label = self.desc_path(n))
+
         self.menubar = tkinter.Menu(self.master)
         self.master.configure(menu = self.menubar)
 
@@ -327,47 +362,12 @@ class App(tkinter.Frame):
         self.menuedit = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menuedit,
                 label = "Edit")
-        self.menuedit.add_command(
-                command = lambda: self.open_path("/parts"),
-                label = "Select part")
-        self.menuedit.add_separator()
-        self.menuedit.add_command(
-                command = lambda: self.open_path("/res"),
-                label = "Resources")
-        self.menuedit.add_command(
-                command = lambda: self.open_path("/objs"),
-                label = "Objects")
-        self.menuedit.add_command(
-                command = lambda: self.open_path("/scenes"),
-                label = "Scenes")
-        self.menuedit.add_command(
-                command = lambda: self.open_path("/names"),
-                label = "Names")
-        self.menuedit.add_command(
-                command = lambda: self.open_path("/invntr"),
-                label = "Invntr")
-        self.menuedit.add_command(
-                command = lambda: self.open_path("/casts"),
-                label = "Casts")
-        self.menuedit.add_command(
-                command = lambda: self.open_path("/msgs"),
-                label = "Messages")
-        self.menuedit.add_command(
-                command = lambda: self.open_path("/dlgs"),
-                label = "Dialog groups")
-        self.menuedit.add_command(
-                command = lambda: self.open_path("/opcodes"),
-                label = "Opcodes")
-        self.menuedit.add_command(
-                command = lambda: self.open_path("/dlgops"),
-                label = "Dialog opcodes")
-        self.menuedit.add_command(
-                command = lambda: self.open_path("/strs"),
-                label = "Stores")
-        self.menuedit.add_command(
-                command = lambda: self.open_path("/files"),
-                label = "Files")
-
+                
+        editnav = ["/parts", None, "/res", "/objs", "/scenes", "/names", 
+            "/invntr", "/casts", "/msgs", "/dlgs", "/opcodes", "/dlgops", 
+            "/strs", "/files"]
+        mkmenupaths(self.menuedit, editnav)
+        
         self.menunav = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menunav,
                 label = "Navigation")
@@ -409,19 +409,8 @@ class App(tkinter.Frame):
         self.menuhelp = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menuhelp,
                 label = "Help")
-        self.menuhelp.add_command(
-                command = lambda: self.open_path("/help/index"),
-                label = "Contents")
-        self.menuhelp.add_separator()
-        self.menuhelp.add_command(
-                command = lambda: self.open_path("/support"),
-                label = "Support")
-        self.menuhelp.add_command(
-                command = lambda: self.open_path("/info"),
-                label = "Info")
-        self.menuhelp.add_command(
-                command = lambda: self.open_path("/about"),
-                label = "About")
+        helpnav = ["/help/index", None, "/support", "/info", "/about"]
+        mkmenupaths(self.menuhelp, helpnav)
 
     def update_after(self):
         if not self.need_update:
@@ -450,7 +439,7 @@ class App(tkinter.Frame):
     def on_resize_view(self, event):
         self.update_after()
  
-    def open_path(self, loc, withhist = True):
+    def parse_path(self, loc):
         if isinstance(loc, str):
             path = []
             if loc[:1] == "/":
@@ -466,11 +455,13 @@ class App(tkinter.Frame):
         path = tuple(path)
         while path[-1:] == ("",):
             path = path[:-1]
-
+        return path    
+ 
+    def open_path(self, loc, withhist = True):
+        path = self.parse_path(loc)        
         if withhist:
             self.hist.append([path])
             self.histf = []
-
         print("DEBUG: Open", path)
         self.curr_path = path
         if len(path) > 0:
@@ -479,9 +470,20 @@ class App(tkinter.Frame):
             self.curr_help = ""
         if len(path) > 0:
             if path[0] in self.path_handler:
-                return self.path_handler[path[0]](path)
+                return self.path_handler[path[0]][0](path)
         return self.path_default(path)
 
+    def desc_path(self, loc):
+        path = self.parse_path(loc)
+        if len(path) > 0:
+            if path[0] in self.path_handler:
+                desc = self.path_handler[path[0]][1]
+                if callable(desc):
+                    return desc(path)
+                elif desc:
+                    return desc
+        return self.desc_default(path)
+
     def update_canvas(self):
         if self.curr_main == 0:          
             return
@@ -923,22 +925,22 @@ class App(tkinter.Frame):
         self.switch_view(0)
         self.clear_info()
         self.add_info("<b>History</b>\n\n")
-        def phist(h):
-            d = ""
-            for e in h:
-                d += "/{}".format(e)
-            return d
         for idx, h in enumerate(self.hist[:-1]):
             self.add_info(" {:5d}) {}\n".format(idx - len(self.hist) + 1, 
-                phist(h[0])))
-        self.add_info(" -----> {}\n".format(phist(self.curr_path)))
+                self.desc_path(h[0])))
+        self.add_info(" -----> {}\n".format(self.desc_path(self.curr_path)))
         
         for idx, h in enumerate(self.histf):
-            self.add_info(" {:5d}) {}\n".format(idx + 1, phist(h[0])))
-            
+            self.add_info(" {:5d}) {}\n".format(idx + 1, self.desc_path(h[0])))
         
+    def desc_default(self, path):
+        desc = ""
+        for item in path:
+            desc += "/{}".format(item)
+        if not desc:
+            desc = "Outline"
+        return desc
         
-                
     def path_default(self, path):
         self.switch_view(0)
         self.update_gui("Outline")
@@ -977,6 +979,22 @@ class App(tkinter.Frame):
             ]
             for name, act in acts:
                 self.insert_lb_act(name, act)
+        return True
+
+    def desc_parts(self, path):
+        # this part can be internationalized
+        if len(path) > 1:
+            try:
+                part = path[1]
+                part = part.split(".", 1)
+                part[0] = int(part[0])
+                part[1] = int(part[1])
+            except:
+                part = None
+                pass
+                return "Goto to unknown part {}".format(path[1])
+            return "Select part {} chapter {}".format(part[0], part[1])
+        return "Select part"
 
     def path_parts(self, path):
         if self.sim is None:
@@ -1037,6 +1055,8 @@ class App(tkinter.Frame):
             except:
                 self.add_info("Error open part {} chapter {} - \n\n{}".\
                     format(part[0], part[1], hlesc(traceback.format_exc())))
+                return False
+        return True
         
 
     def path_res(self, path):
@@ -1108,7 +1128,6 @@ class App(tkinter.Frame):
                                 flc.frame_num, flc.delay))
                 else:
                     self.add_info("No information availiable")
-
             except:
                 self.add_info("Error loading {} - \"{}\" \n\n{}".\
                     format(res_id, hlesc(fn), hlesc(traceback.format_exc())))
@@ -1129,9 +1148,12 @@ class App(tkinter.Frame):
             usedby(self.sim.objects)
             self.add_info("\n<b>Used by scenes</b>:\n")
             usedby(self.sim.scenes)
+            self.add_info("\nFile: {}\n".format(self.fmt_hl_file(fn)))
+            
                     
         elif mode[0] == "view":
             self.path_res_view(res_id)
+        return True
                 
     def path_res_view(self, res_id):
         fn = self.sim.res[res_id]
@@ -1166,6 +1188,7 @@ class App(tkinter.Frame):
         finally:
             if dataf:
                 dataf.close()
+        return True
 
     def path_res_status(self):
         self.switch_view(0)
@@ -1184,6 +1207,7 @@ class App(tkinter.Frame):
             self.add_info("  <a href=\"/res/flt/{}\">{}</a>: {}\n".format(
                 ft, ft, fts[ft]))
         self.select_lb_item(None)
+        return True
     
     def on_path_res_info(self):
         self.switch_view(0)
@@ -1201,9 +1225,9 @@ class App(tkinter.Frame):
                 res_id, self.sim.res[res_id]), ["res", "all", res_id], res_id)
         # change                
         if len(path) > 2:
-            self.path_res_open(path[:3], path[2], path[3:])
+            return self.path_res_open(path[:3], path[2], path[3:])
         else:
-            self.path_res_status()
+            return self.path_res_status()
 
     def path_res_flt(self, path):
         lst = []
@@ -1220,9 +1244,9 @@ class App(tkinter.Frame):
                     res_id)
         # change                
         if len(path) > 3:
-            self.path_res_open(path[:4], path[3], path[4:])
+            return self.path_res_open(path[:4], path[3], path[4:])
         else:
-            self.path_res_status()
+            return self.path_res_status()
 
     def path_objs_scenes(self, path):
         if self.sim is None:
@@ -1432,10 +1456,8 @@ class App(tkinter.Frame):
                         self.fmt_hl_scene(sf.idx, True)))
                     self.add_info("    <i>on</i>: {}\n".format(
                         self.fmt_hl_obj(oo.idx, True)))
+        return True
                 
-                
-                
-
     def path_std_items(self, path, level, guiname, guiitem, tt, lst, lst_idx, 
             lbmode, cb):
         self.switch_view(0)
@@ -1444,7 +1466,6 @@ class App(tkinter.Frame):
             for idx, name in enumerate(lst_idx):
                 lb = self._t(name, tt)
                 if lbmode == 1:
-                    print(name, )
                     lb = "{} - {}".format(name, self._t(lst[name], tt))
                 self.insert_lb_act(lb, path[:level] + tuple([idx]), idx)
         # change
@@ -1465,6 +1486,7 @@ class App(tkinter.Frame):
         else:
             # info
             cb(name)
+        return True
         
     def path_names(self, path):
         if self.sim is None:
@@ -1578,12 +1600,15 @@ class App(tkinter.Frame):
                     lst.append((k, msg.msg_wav, idx, msg.name))
                 lst.sort()
                 for _, wav, idx, capt in lst:
-                    self.add_info("  <a href=\"/msgs/{}\">{}</a> - {}\n".
-                        format(idx, wav, capt))
+                    self.add_info("  <a href=\"/msgs/{}\">{}</a> - {} - {}\n".
+                        format(idx, idx, self.fmt_hl_file("speech{}/{}".format(
+                            self.sim.curr_part, wav), wav), capt))
             else:
                 # msg info
                 self.add_info("<b>Message</b>: {}\n".format(path[1]))
-                self.add_info("  wav:    {}\n".format(msg.msg_wav))
+                self.add_info("  wav:    {}\n".
+                        format(self.fmt_hl_file("speech{}/{}".format(
+                            self.sim.curr_part, msg.msg_wav), msg.msg_wav)))
                 self.add_info("  object: " + self.fmt_hl_obj(msg.obj.idx, 
                     True) + "\n")
                 self.add_info("  arg2:   {a} (0x{a:X})\n".format(
@@ -1630,6 +1655,7 @@ class App(tkinter.Frame):
             self.curr_state["btnsort"] = [[b1, 0], [b2, 1], [b3, 2]]
         # change
         upd_msgs()
+        return True
 
     def path_dlgs(self, path):
         if self.sim is None:
@@ -1776,6 +1802,7 @@ class App(tkinter.Frame):
             usedby(self.sim.objects)
             self.add_info("\n<b>Used by scenes</b>:\n")
             usedby(self.sim.scenes)
+        return True
         
     def path_opcodes(self, path):
         if self.sim is None:
@@ -1901,6 +1928,7 @@ class App(tkinter.Frame):
                         aidx, self.fmt_cmt("// " + self.fmt_hl_obj_scene(
                         obj_idx, True))))
                 self.add_info("\n")  
+        return True
 
     def path_dlgops(self, path):
         if self.sim is None:
@@ -1970,7 +1998,6 @@ class App(tkinter.Frame):
                         for oidx, op in enumerate(dlg.ops):
                             if op.opcode == opcode:
                                 dls.append([act.ref, grp.idx, aidx, didx, oidx])
-
             # display
             if len(dls) == 0:
                 self.add_info("<i>Not used in dialogs</i>\n\n")
@@ -1983,7 +2010,14 @@ class App(tkinter.Frame):
                         aidx, didx, oidx, self.fmt_cmt("// " + 
                         self.fmt_hl_obj_scene(obj_idx, True))))
                 self.add_info("\n")  
+        return True
 
+    def fmt_hl_file(self, fn, capt = None):
+        fnl = fn.lower().replace("\\", "/")
+        capt = capt or fn
+        fid = urllib.parse.quote_plus(fnl)
+        return "<a href=\"/files/{}\">{}</a>".format(fid, hlesc(capt))                   
+        
     def path_stores(self, path):
         if self.strfm is None:
             return self.path_default([])
@@ -2049,8 +2083,8 @@ class App(tkinter.Frame):
         if stid is None:
             self.add_info("<b>Stores</b>\n\n")
             for idx, st in enumerate(self.strfm.strfd):
-                self.add_info("  {}) <a href=\"/strs/{}\">{}</a> - {}\n".format(
-                    idx + 1, idx, st[1], st[2]))
+                self.add_info("  {}) <a href=\"/strs/{}\">{}</a> (tag={})\n".
+                    format(idx + 1, idx, st[1], st[2]))
         else:
             if stid >= len(self.strfm.strfd):
                 self.add_info("<b>Store</b> \"{}\" not found\n\n".\
@@ -2061,10 +2095,17 @@ class App(tkinter.Frame):
             self.add_info("<b>Store</b>: {}\n".format(name))
             self.add_info("  Files: <a href=\"/files\">{}</a>, Tag: {}\n\n".
                 format(len(strlst), tag))
-            for idx, (fname, ford, pos, ln) in enumerate(strlst):
-                self.add_info("  {}) <a href=\"/files/{}\">{}</a> "
-                    "(pos={}, len={})\n".format(idx + 1, ford, fname, 
-                    pos, ln))
+            for idx, (fname, _, _, _) in enumerate(strlst):
+                self.add_info("  {}) {}\n".format(idx + 1, 
+                    self.fmt_hl_file(fname)))
+        return True
+
+    def desc_files(self, path):
+        # this part can be internationalized
+        if len(path) > 1:
+            fnl = urllib.parse.unquote(path[1])
+            return "File \"{}\"".format(fnl)
+        return "Files"
 
     def path_files(self, path):
         if self.strfm is None:
@@ -2073,89 +2114,124 @@ class App(tkinter.Frame):
             path = self.curr_path
             fid = None
             if len(path) > 1:
+                # normalize fn
+                fnl = urllib.parse.unquote(path[1]).replace("\\","/").lower()
+                fid = urllib.parse.quote_plus(fnl)
                 # index
-                self.select_lb_item(path[1])
-                try:
-                    fid = path[1]
-                except:
-                    pass
+                self.select_lb_item(fid)
             else:
                 self.select_lb_item(None)
             self.clear_info()
             if fid is None:
-                self.add_info("<b>Files</b>\n\n")
+                self.add_info("<b>Files in all stores</b>\n\n")
                 for idx, fn in enumerate(self.strfm.strtableord):
-                    stid, pos, ln = self.strfm.strtable[fn]
-                    self.add_info("  {}) <a href=\"/files/{}\">{}</a>\n".format(
-                        idx + 1, idx, hlesc(fn)))
+                    stid, _, _ = self.strfm.strtable[fn]
+                    self.add_info("  {}) {}\n".format(
+                        idx + 1, self.fmt_hl_file(fn)))
             else:
-                if fid >= len(self.strfm.strtable):
+                try:
+                    self.strfm.read_file(fnl)
+                except:
                     self.add_info("<b>File</b> \"{}\" not found\n\n".\
-                        format(path[1]))
+                        format(hlesc(fnl)))
                     return
-                fn = self.strfm.strtableord[fid]
-                stid, pos, ln = self.strfm.strtable[fn]
-
-                self.add_info("<b>File</b>: {}\n\n".format(fn))
-                self.add_info("  Store: <a href=\"/strs/{}\">{}</a>\n".format(
-                    stid, self.strfm.strfd[stid][1]))
-                self.add_info("  Pos: {} (0x{:x})\n  Len: {} (0x{:x})\n".format(
-                    pos, pos, ln, ln))
-                fnl = fn.lower().replace("\\", "/")
+                self.add_info("<b>File</b>: {}\n\n".format(fnl))
+                    
+                # search in loaded stores
+                if fnl in self.strfm.strtable:
+                    # in store
+                    stid, pos, ln = self.strfm.strtable[fnl]
+                    self.add_info("  Store: <a href=\"/strs/{}\">{}</a>\n".format(
+                        stid, self.strfm.strfd[stid][1]))
+                    self.add_info("  Pos: {} (0x{:x}), Len: {} (0x{:x})\n".format(
+                        pos, pos, ln, ln))
+                else:
+                    # on disk
+                    self.add_info("  Loaded from disk\n")
+
                 if self.sim:
-                    self.add_info("\n<b>Used in resources</b>:\n")
+                    wascapt = False
                     for resid in self.sim.resord:
                         resfn = self.sim.res[resid].lower().replace("\\", "/")
                         if resfn == fnl:
+                            if not wascapt:
+                                self.add_info("\n<b>Used in resources</b>:\n")
+                                wascapt = True
                             self.add_info("  {} - <a href=\"/res/all/{}\">"\
                                 "{}</a>\n".format(resid, resid, 
                                 hlesc(self.sim.res[resid])))
+                    wavpref = "speech{}/".format(self.sim.curr_part)
+                    if fnl[-4:] == ".wav" and fnl.startswith(wavpref):
+                        wascapt = False
+                        for idx, msg in enumerate(self.sim.msgs):
+                            if fnl == wavpref + msg.msg_wav.lower():
+                                if not wascapt:
+                                    self.add_info("\n<b>Used in messages</b>:"
+                                        "\n")
+                                    wascapt = True
+                                    self.add_info("  {}\n".format(
+                                        self.fmt_hl_msg(idx, True)))
+
                 grp = [
                     [".leg", ".off", ".msk", ".flc"],
-                    [".bmp", "cvx"]
+                    [".bmp", ".cvx"]
                 ]
                 sg = None
                 for g in grp:
                     if fnl[-4:] in g:
                         sg = g
                 if sg:
-                    self.add_info("\n<b>Related file</b>:\n")
+                    self.add_info("\n<b>Related file(s)</b>:\n")
                     rel = []
-                    for idx, fnm in enumerate(self.strfm.strtableord):
-                        fnml = fnm.lower().replace("\\", "/")
-                        if idx == fid: continue
-                        if fnml[:-4] == fnl[:-4] and fnml[-4:] in sg:
-                            self.add_info("  <a href=\"/files/{}\">{}</a>\n".
-                                format(idx, hlesc(fnm)))
-                # spec files
-                sfils = {
-                    "script.dat": ["/objs", "/scenes", "/opcodes"], 
-                    "background.bg": ["/objs", "/scenes", "/opcodes"], 
-                    "cast.ini": ["/casts"],
-                    "invntr.txt": ["/invntr"],
-                    "names.ini": ["/names"],
-                    "bgs.ini": ["/bgs"],
-                    "dialogue.fix": ["/dlgs", "/msgs", "/dlgops"],
-                    "dialogue.lod": ["/dlgs", "/msgs", "/dlgops"],
-                    "resource.qrc": ["/res"],
-                }
-                for k, v in sfils.items():
-                    if fnl.endswith(k):
-                        self.add_info("\n<b>Related info</b>:\n")
-                        for p in v:
-                            self.add_info("  {}\n".format(p))
+                    for ext in sg:
+                        if ext == fnl[-4:]: continue
+                        if self.strfm.exists(fnl[:-4] + ext):
+                            self.add_info("  {}\n".format(self.fmt_hl_file(
+                                fnl[:-4] + ext)))
+                # special files
+                fns = fnl.split("/")
+                last = ""
+                while len(fns) > 0:
+                    if fns[-1:] == [""]:
+                        continue
+                    last = fns[-1:][0]
+                    break
+                sa = None
+                if last in ["script.dat", "backgrnd.bg", "bgdata.dat", "bgedit.bg"]:
+                    sa = ["/objs", "/scenes", "/opcodes"]
+                if last == "cast.ini":
+                    sa = ["/objs", "/casts"]
+                if last == "names.ini":
+                    sa = ["/objs", "/names"]
+                if last == "invntr.txt":
+                    sa = ["/objs", "/invntr"]
+                if last == "bgs.ini":
+                    sa = ["/scenes", "/bgs"]
+                if last in ["dialogue.fix", "dialogue.lod"]:
+                    sa = ["/dlgs", "/msgs", "/dlgops"]
+                if last == "resource.qrc":
+                    sa = ["/res", "/files"]
+                if last == "parts.ini":
+                    sa = ["/parts"]
+                if last[:4] == "disk" and last[-3:] == ".id":
+                    sa = ["/parts"]
+                if sa:
+                    self.add_info("\n<b>See also</b>:\n")
+                    for p in sa:
+                        self.add_info("  <a href=\"{}\">{}</a>\n".format(p, 
+                            self.desc_path(p)))
                         
-                
-                    
-            
         self.switch_view(0)
         keys = None
         if self.last_path[:1] != ("files",):
             # calc statistics
             self.update_gui("Files ({})".format(len(self.strfm.strtable)))
             for idx, fn in enumerate(self.strfm.strtableord):
-                self.insert_lb_act(fn, ["files", idx], idx)
+                fnl = fn.lower().replace("\\", "/")
+                fnl = urllib.parse.quote_plus(fnl)
+                self.insert_lb_act(fn, ["files", fnl], fnl)
         upd_files()
+        return True
             
             
     def path_test(self, path):
@@ -2240,6 +2316,7 @@ class App(tkinter.Frame):
             self.select_lb_item(None)
         # display
         display_page()
+        return True
 
     def path_about(self, path):
         self.switch_view(0)
@@ -2252,6 +2329,7 @@ class App(tkinter.Frame):
         self.add_info("  https://bitbucket.org/romiq/p12simtran\n")
         self.add_info("\n")
         self.path_info_outline()
+        return True
 
     def path_support(self, path):
         self.switch_view(0)
@@ -2297,6 +2375,15 @@ class App(tkinter.Frame):
 
         if self.sim or self.strfm:
             self.path_info_outline()
+
+        return True
+            
+    def desc_help(self, path):
+        if path == ("help",):
+            path = ("help", "index")
+        if path == ("help", "index"):
+            return "Index"
+        return "Help for " + self.desc_path(path[1])
             
     def path_help(self, path):
         self.switch_view(0)
@@ -2351,6 +2438,8 @@ class App(tkinter.Frame):
                     format(hlesc(hfn), hlesc(traceback.format_exc())))
         else:
             self.select_lb_item(None)
+
+        return True
         
     def path_info(self, path):
         self.switch_view(0)
@@ -2389,6 +2478,8 @@ class App(tkinter.Frame):
             else: 
                 self.add_info("Unknown data type \"{}\"\n".format(hlesc(name)))
 
+        return True
+
     def on_open_data(self):
         ft = [\
             ('all files', '.*')]
@@ -2529,6 +2620,7 @@ class App(tkinter.Frame):
                     if pref[0] in self.tran:
                         self.tran[pref[0]][tr.msgid] = tr.msgstr
                     self.tran["_"][tr.msgid] = tr.msgstr
+            return True
         except:
             self.switch_view(0)
             self.clear_info()
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 1b6715cbd..b5a9685f0 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -275,7 +275,7 @@ class Engine:
                 if "StartRoom" in self.bgs_ini["Settings"]:
                     self.start_scene = self.bgs_ini["Settings"]["StartRoom"]
         # load .STR
-        strs = ["Flics", "Background", "Wav", "Music", "SFX"]
+        strs = ["Flics", "Background", "Wav", "Music", "SFX", "Speech"]
         for strf in strs:
             #pf = self.fman.find_path(self.curr_path + "bgs.ini")
             #if not pf: continue


Commit: f5c4aa3cdc75499e97617bf5d9f3cedd6c8d3ec8
    https://github.com/scummvm/scummvm-tools/commit/f5c4aa3cdc75499e97617bf5d9f3cedd6c8d3ec8
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: sort files, files in stores

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index f74e2cb16..742bbac07 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -12,6 +12,9 @@
 Изменено отображение списка сообщений, добавлен переход на wav файл
 Движок: автоматическая загрузка файлов speech из хранилищ
 Исправлена последовательная загрузка разделов
+Сортировка списка всех файлов
+Переход от хранилища к первому файлу в общем списке
+Сортировка списка файлов в хранилище
 
 2014-12-20 версия 0.3e
 ----------------------
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index ee372fb6d..1d910d3de 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -780,6 +780,30 @@ class App(tkinter.Frame):
         self.curr_gui.append(lambda:lab.pack_forget())
         return lab
 
+    def add_toolgrp(self, label, glkey, items, cbupd):
+        def makecb(v, g):
+            def btncb():
+                self.gl_state[g] = v
+                cbupd()
+            return btncb
+        if label:
+            self.add_toollabel(label)
+        kl = list(items.keys())
+        kl.sort()
+        res = []
+        for k in kl:
+            b = self.add_toolbtn(items[k], makecb(k, glkey))
+            res.append([b, k])
+        return res
+
+    def upd_toolgrp(self, btns, state):
+        for btn, idx in btns:
+            if idx != state and state != -1:
+                btn.config(state = tkinter.NORMAL)
+            else:
+                btn.config(state = tkinter.DISABLED)
+    
+
     def clear_hist(self):
         self.hist = self.hist[-1:]
         self.histf = []
@@ -1577,11 +1601,7 @@ class App(tkinter.Frame):
             sm = self.gl_state.get("msgs.sort", 0)
             if msg:
                 sm = -1
-            for btn, idx in self.curr_state["btnsort"]:
-                if idx != sm and sm != -1:
-                    btn.config(state = tkinter.NORMAL)
-                else:
-                    btn.config(state = tkinter.DISABLED)
+            self.upd_toolgrp(self.curr_state["btnsort"], sm)
             self.clear_info()
             if not msg:
                 if len(path) > 1:
@@ -1639,20 +1659,8 @@ class App(tkinter.Frame):
                     capt = capt[:40] + "|"
                 self.insert_lb_act("{} - {}".format(msg.idx, capt),
                     ["msgs", idx], idx)
-            def sfn():
-                self.gl_state["msgs.sort"] = 0
-                upd_msgs()
-            def sidx():
-                self.gl_state["msgs.sort"] = 1
-                upd_msgs()
-            def stxt():
-                self.gl_state["msgs.sort"] = 2
-                upd_msgs()
-            self.add_toollabel("Sort by")
-            b1 = self.add_toolbtn("wav", sfn)
-            b2 = self.add_toolbtn("order", sidx)
-            b3 = self.add_toolbtn("text", stxt)
-            self.curr_state["btnsort"] = [[b1, 0], [b2, 1], [b3, 2]]
+            self.curr_state["btnsort"] = self.add_toolgrp("Sort by", 
+                "msgs.sort", {0: "wav", 1: "order", 2: "text"}, upd_msgs)
         # change
         upd_msgs()
         return True
@@ -2021,6 +2029,57 @@ class App(tkinter.Frame):
     def path_stores(self, path):
         if self.strfm is None:
             return self.path_default([])
+        def upd_strs():
+            path = self.curr_path
+            # change
+            stid = None
+            if len(path) > 1:
+                # index
+                self.select_lb_item(path[1])
+                try:
+                    stid = path[1]
+                except:
+                    pass
+            else:
+                self.select_lb_item(None)
+            # display
+            self.clear_info()
+            sm = self.gl_state.get("strs.sortfiles", 0)
+            if stid is None:
+                sm = -1
+            self.upd_toolgrp(self.curr_state["btnsort"], sm)
+            self.curr_state["btnext"].config(state = tkinter.DISABLED)
+            if stid is None:
+                self.add_info("<b>Stores</b>\n\n")
+                for idx, st in enumerate(self.strfm.strfd):
+                    self.add_info("  {}) <a href=\"/strs/{}\">{}</a>"
+                        " (tag={})\n".
+                        format(idx + 1, idx, st[1], st[2]))
+            else:
+                if stid >= len(self.strfm.strfd):
+                    self.add_info("<b>Store</b> \"{}\" not found\n\n".\
+                        format(path[1]))
+                    return
+                self.curr_state["btnext"].config(state = tkinter.NORMAL)
+                _, name, tag, strlst = self.strfm.strfd[stid]
+                self.add_info("<b>Store</b>: {}\n".format(name))
+                flnk = "{}".format(len(strlst))
+                if len(strlst):
+                    flnk = self.fmt_hl_file(strlst[0][0], flnk)
+                self.add_info("  Files: {}, Tag: {}\n\n".format(flnk, tag))
+                
+                lst = []
+                for idx, (fname, _, _, _) in enumerate(strlst):
+                    if sm == 0:
+                        k = idx
+                    else:
+                        k = fname.lower().replace("\\", "/")
+                    lst.append((k, idx, fname))
+                lst.sort()
+                for _, idx, fname in lst:
+                    self.add_info("  {:5}) {}\n".format(idx + 1, 
+                        self.fmt_hl_file(fname)))
+            
         self.switch_view(0)
         keys = None
         if self.last_path[:1] != ("strs",):
@@ -2066,38 +2125,9 @@ class App(tkinter.Frame):
                             format(hlesc(fname), hlesc(traceback.format_exc())))
                         return
             self.curr_state["btnext"] = self.add_toolbtn("Extract STR", ext_str)
-        # change
-        stid = None
-        if len(path) > 1:
-            # index
-            self.select_lb_item(path[1])
-            try:
-                stid = path[1]
-            except:
-                pass
-        else:
-            self.select_lb_item(None)
-        # display
-        self.clear_info()
-        self.curr_state["btnext"].config(state = tkinter.DISABLED)
-        if stid is None:
-            self.add_info("<b>Stores</b>\n\n")
-            for idx, st in enumerate(self.strfm.strfd):
-                self.add_info("  {}) <a href=\"/strs/{}\">{}</a> (tag={})\n".
-                    format(idx + 1, idx, st[1], st[2]))
-        else:
-            if stid >= len(self.strfm.strfd):
-                self.add_info("<b>Store</b> \"{}\" not found\n\n".\
-                    format(path[1]))
-                return
-            self.curr_state["btnext"].config(state = tkinter.NORMAL)
-            _, name, tag, strlst = self.strfm.strfd[stid]
-            self.add_info("<b>Store</b>: {}\n".format(name))
-            self.add_info("  Files: <a href=\"/files\">{}</a>, Tag: {}\n\n".
-                format(len(strlst), tag))
-            for idx, (fname, _, _, _) in enumerate(strlst):
-                self.add_info("  {}) {}\n".format(idx + 1, 
-                    self.fmt_hl_file(fname)))
+            self.curr_state["btnsort"] = self.add_toolgrp("Sort by", 
+                "strs.sortfiles", {0: "order", 1: "filename"}, upd_strs)
+        upd_strs()
         return True
 
     def desc_files(self, path):
@@ -2122,11 +2152,23 @@ class App(tkinter.Frame):
             else:
                 self.select_lb_item(None)
             self.clear_info()
+            sm = self.gl_state.get("files.sort", 0)
+            if fid is not None:
+                sm = -1
+            self.upd_toolgrp(self.curr_state["btnsort"], sm)
             if fid is None:
                 self.add_info("<b>Files in all stores</b>\n\n")
+                lst = []
                 for idx, fn in enumerate(self.strfm.strtableord):
-                    stid, _, _ = self.strfm.strtable[fn]
-                    self.add_info("  {}) {}\n".format(
+                    if sm == 0:
+                        k = idx
+                    else:
+                        k = fn.lower().replace("\\", "/")
+                    lst.append((k, idx, fn))
+                lst.sort()
+                for _, idx, fn in lst:
+                    #stid, _, _ = self.strfm.strtable[fn]
+                    self.add_info("  {:5}) {}\n".format(
                         idx + 1, self.fmt_hl_file(fn)))
             else:
                 try:
@@ -2230,6 +2272,8 @@ class App(tkinter.Frame):
                 fnl = fn.lower().replace("\\", "/")
                 fnl = urllib.parse.quote_plus(fnl)
                 self.insert_lb_act(fn, ["files", fnl], fnl)
+            self.curr_state["btnsort"] = self.add_toolgrp("Sort by", 
+                "files.sort", {0: "order", 1: "filename"}, upd_files)
         upd_files()
         return True
             
@@ -2241,6 +2285,10 @@ class App(tkinter.Frame):
             if len(path) > 2:
                 item = path[2]
             self.clear_info()
+            sm = self.gl_state.get("test.info.mode", 0)
+            if item is None:
+                sm = -1
+            self.upd_toolgrp(self.curr_state["gbtns"], sm)
             if item is None:
                 self.switch_view(0)
                 self.add_info("Select item " + path[1])
@@ -2254,7 +2302,7 @@ class App(tkinter.Frame):
                     self.add_info("Local mode {}\n".format(
                         self.curr_state.get("mode", None)))
                     self.add_info("Global mode {}\n".format(
-                        self.gl_state.get("mode", None)))
+                        self.gl_state.get("test.info.mode", None)))
                     for i in range(100):
                         self.add_info("  Item {}\n".format(i))
             
@@ -2278,30 +2326,11 @@ class App(tkinter.Frame):
                 self.curr_state["btn1"].config(state = tkinter.NORMAL)
                 self.curr_state["btn2"].config(state = tkinter.DISABLED)
                 display_page()
-            def sw_gmode1():
-                print("Global Mode 1")
-                self.gl_state["test.info.mode"] = 0
-                self.curr_state["gbtn1"].config(state = tkinter.DISABLED)
-                self.curr_state["gbtn2"].config(state = tkinter.NORMAL)
-                display_page()
-            def sw_gmode2():
-                print("Global Mode 2")
-                self.gl_state["test.info.mode"] = 1
-                self.curr_state["gbtn1"].config(state = tkinter.NORMAL)
-                self.curr_state["gbtn2"].config(state = tkinter.DISABLED)
-                display_page()
             self.curr_state["btn1"] = self.add_toolbtn("Mode 1", sw_mode1)
             self.curr_state["btn2"] = self.add_toolbtn("Mode 2", sw_mode2)
             # we store buttons in local state
-            st = self.gl_state.get("test.info.mode", 0)
-            b = self.add_toolbtn("Global Mode 1", sw_gmode1)
-            if st == 0:
-                b.config(state = tkinter.DISABLED)
-            self.curr_state["gbtn1"] = b
-            b = self.add_toolbtn("Global Mode 2", sw_gmode2)
-            if st == 1:
-                b.config(state = tkinter.DISABLED)
-            self.curr_state["gbtn2"] = b
+            self.curr_state["gbtns"] = self.add_toolgrp(None, "test.info.mode",
+                {0: "mode 1", 1: "mode 2", 2: "mode 3"}, display_page)
 
         # change
         item = None


Commit: 65bbf7768e940ae3799735c32e8a25dcf1fb35aa
    https://github.com/scummvm/scummvm-tools/commit/65bbf7768e940ae3799735c32e8a25dcf1fb35aa
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fix long lists

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 742bbac07..81f89ee8c 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -15,6 +15,7 @@
 Сортировка списка всех файлов
 Переход от хранилища к первому файлу в общем списке
 Сортировка списка файлов в хранилище
+Отступ от правого края для больших списков выбирается автоматически
 
 2014-12-20 версия 0.3e
 ----------------------
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 1d910d3de..7e43ec3a5 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -9,6 +9,7 @@ from tkinter import ttk, font, filedialog, messagebox
 from idlelib.WidgetRedirector import WidgetRedirector
 import traceback
 import urllib.parse
+import math
 
 # Image processing
 try:
@@ -51,6 +52,17 @@ def fmt_arg(value):
     else:
         return "0x{:X}".format(value)
     
+def fmt_dec(value, add = 0):
+    return "{{:{}}}".format(fmt_dec_len(value, add))
+        
+def fmt_dec_len(value, add = 0):
+    if value == 0:
+        d = 1
+    else:
+        d = int(math.log10(value)) + 1
+    d += add
+    return d
+    
 def translit(text):
     ru = "абвгдеёзийклмнопрстуфхъыьэАБВГДЕЁЗИЙКЛМНОПРСТУФХЪЫЬЭ"
     en = "abvgdeezijklmnoprstufh'y'eABVGDEEZIJKLMNOPRSTUFH'Y'E"
@@ -949,14 +961,17 @@ class App(tkinter.Frame):
         self.switch_view(0)
         self.clear_info()
         self.add_info("<b>History</b>\n\n")
+        sz = max(len(self.hist[:-1]), len(self.histf))
+        fmt = fmt_dec(sz, 1)
+        fmt = "  " + fmt + ") {}\n"
         for idx, h in enumerate(self.hist[:-1]):
-            self.add_info(" {:5d}) {}\n".format(idx - len(self.hist) + 1, 
+            self.add_info(fmt.format(idx - len(self.hist) + 1, 
                 self.desc_path(h[0])))
-        self.add_info(" -----> {}\n".format(self.desc_path(self.curr_path)))
-        
+        self.add_info(" {} {}\n".format("=" * fmt_dec_len(sz, 2) + ">", 
+            self.desc_path(self.curr_path)))
         for idx, h in enumerate(self.histf):
-            self.add_info(" {:5d}) {}\n".format(idx + 1, self.desc_path(h[0])))
-        
+            self.add_info(fmt.format(idx + 1, self.desc_path(h[0])))
+            
     def desc_default(self, path):
         desc = ""
         for item in path:
@@ -1365,8 +1380,9 @@ class App(tkinter.Frame):
                 else:
                     self.add_info("\n<b>References</b>: {}\n".\
                         format(len(rec.refs)))
+                fmtd = "  " + fmt_dec(len(rec.refs)) + ") "
                 for idx, ref in enumerate(rec.refs):
-                    self.add_info("  {}) ".format(idx) + 
+                    self.add_info(fmtd.format(idx) + 
                         self.fmt_hl_obj(ref[0].idx))
                     msg = ""
                     for arg in ref[1:]:
@@ -1383,6 +1399,8 @@ class App(tkinter.Frame):
             resused = []
             dlgused = []
             self.add_info("\n<b>Handlers</b>: {}\n".format(len(rec.acts)))
+            fmtra = "  " + fmt_dec(len(rec.acts)) + \
+                ") <u>on {}</u>, ops: {}{}\n"
             for idx, act in enumerate(rec.acts):
                 msg = self.fmt_opcode(act.act_op)
                 cmt = ""
@@ -1398,10 +1416,10 @@ class App(tkinter.Frame):
                         else:
                             act_ref = "0x{:X}".format(act.act_ref)
                     msg += " 0x{:02X} {}".format(act.act_status, act_ref)
-                self.add_info("  {}) <u>on {}</u>, ops: {}{}\n".format(\
-                    idx, msg, len(act.ops), cmt))
+                self.add_info(fmtra.format(idx, msg, len(act.ops), cmt))
+                fmtao = "    " + fmt_dec(len(act.ops)) + " {} "
                 for oidx, op in enumerate(act.ops):
-                    self.add_info("    {}) {} ".format(oidx, 
+                    self.add_info(fmtao.format(oidx, 
                         self.fmt_opcode(op.op_code)))
                     cmt = ""
                     if op.op_ref == rec.idx:
@@ -1699,15 +1717,18 @@ class App(tkinter.Frame):
                 grp.idx, grp.idx))
             self.add_info("  arg1: {a} (0x{a:X})\n\n".format(a = grp.grp_arg1))
             self.add_info("<b>Dialog handlers<b>: {}\n".format(len(grp.acts)))
+            fmtga = "  " + fmt_dec(len(grp.acts)) + \
+                ") <u>on {} {} 0x{:X} 0x{:X}</u>, dlgs: {}{}\n"
             for idx, act in enumerate(grp.acts):
-                self.add_info("  {}) <u>on {} {} 0x{:X} 0x{:X}</u>, dlgs: "\
-                    "{}{}\n".format(idx, self.fmt_opcode(act.opcode), 
+                self.add_info(fmtga.format(idx, self.fmt_opcode(act.opcode), 
                         self.fmt_hl_obj(act.ref), act.arg1, act.arg2, \
                         len(act.dlgs), self.fmt_cmt(" // " + 
                             self.fmt_hl_obj(act.ref, True))))
+                fmtad = "    " + fmt_dec(len(act.dlgs)) + \
+                    ") <i>0x{:X} 0x{:X}</i>, ops: {}\n"
                 for didx, dlg in enumerate(act.dlgs):
-                    self.add_info("    {}) <i>0x{:X} 0x{:X}</i>, ops: {}\n".\
-                        format(didx, dlg.arg1, dlg.arg2, len(dlg.ops)))
+                    self.add_info(fmtad.format(didx, dlg.arg1, dlg.arg2, 
+                        len(dlg.ops)))
                     # scan for used adreses
                     usedadr = []
                     for op in dlg.ops:
@@ -1906,8 +1927,10 @@ class App(tkinter.Frame):
                 self.add_info("<i>Not used in scripts</i>\n\n")
             else:            
                 self.add_info("<i>Used in scripts</i>: {}\n".format(len(ops)))
+                fmtops = "  " + fmt_dec(len(ops)) + \
+                    ") obj={}, act={}, op={} {}\n"
                 for idx, (obj_idx, aidx, oidx) in enumerate(ops):
-                    self.add_info("  {}) obj={}, act={}, op={} {}\n".format(
+                    self.add_info(fmtops.format(
                         idx, self.fmt_hl_obj_scene(obj_idx, False), aidx, oidx,
                         self.fmt_cmt("// " + self.fmt_hl_obj_scene(obj_idx, 
                         True))))
@@ -1917,8 +1940,10 @@ class App(tkinter.Frame):
                 self.add_info("<i>Not used in handlers</i>\n\n")
             else:            
                 self.add_info("<i>Used in handlers</i>: {}\n".format(len(acts)))
+                fmtacts = "  " + fmt_dec(len(acts)) + \
+                    ") obj={}, act={} {}\n"
                 for idx, (obj_idx, aidx) in enumerate(acts):
-                    self.add_info("  {}) obj={}, act={} {}\n".format(
+                    self.add_info(fmtacts.format(
                         idx, self.fmt_hl_obj_scene(obj_idx, False), aidx, 
                         self.fmt_cmt("// " + self.fmt_hl_obj_scene(obj_idx, 
                         True))))
@@ -1929,9 +1954,10 @@ class App(tkinter.Frame):
             else:            
                 self.add_info("<i>Used in dialog handlers</i>: {}\n".format(
                     len(dacts)))
+                fmtdacts = "  " + fmt_dec(len(dacts)) + \
+                    ") obj={}, group=<a href=\"/dlgs/{}\">{}</a>, act={} {}\n"
                 for idx, (obj_idx, gidx, aidx) in enumerate(dacts):
-                    self.add_info("  {}) obj={}, group=<a href=\"/dlgs/{}\">{}"
-                        "</a>, act={} {}\n".format(
+                    self.add_info(fmtdacts.format(
                         idx, self.fmt_hl_obj_scene(obj_idx, False), gidx, gidx, 
                         aidx, self.fmt_cmt("// " + self.fmt_hl_obj_scene(
                         obj_idx, True))))
@@ -2011,9 +2037,11 @@ class App(tkinter.Frame):
                 self.add_info("<i>Not used in dialogs</i>\n\n")
             else:            
                 self.add_info("<i>Used in dialogs</i>: {}\n".format(len(dls)))
+                fmtdls = "  " + fmt_dec(len(dacts)) + \
+                    ") obj={}, group=<a href=\"/dlgs/{}\">{}" + \
+                        "</a>, act={}, dlg={}, op={} {}\n"
                 for idx, (obj_idx, gidx, aidx, didx, oidx) in enumerate(dls):
-                    self.add_info("  {}) obj={}, group=<a href=\"/dlgs/{}\">{}"
-                        "</a>, act={}, dlg={}, op={} {}\n".format(
+                    self.add_info(fmtdls.format(
                         idx, self.fmt_hl_obj_scene(obj_idx, False), gidx, gidx, 
                         aidx, didx, oidx, self.fmt_cmt("// " + 
                         self.fmt_hl_obj_scene(obj_idx, True))))
@@ -2051,10 +2079,10 @@ class App(tkinter.Frame):
             self.curr_state["btnext"].config(state = tkinter.DISABLED)
             if stid is None:
                 self.add_info("<b>Stores</b>\n\n")
+                fmt = "  " + fmt_dec(len(self.strfm.strfd)) + \
+                    ") <a href=\"/strs/{}\">{}</a> (tag={})\n"
                 for idx, st in enumerate(self.strfm.strfd):
-                    self.add_info("  {}) <a href=\"/strs/{}\">{}</a>"
-                        " (tag={})\n".
-                        format(idx + 1, idx, st[1], st[2]))
+                    self.add_info(fmt.format(idx + 1, idx, st[1], st[2]))
             else:
                 if stid >= len(self.strfm.strfd):
                     self.add_info("<b>Store</b> \"{}\" not found\n\n".\
@@ -2076,8 +2104,9 @@ class App(tkinter.Frame):
                         k = fname.lower().replace("\\", "/")
                     lst.append((k, idx, fname))
                 lst.sort()
+                fmt = "  " + fmt_dec(len(lst)) + ") {}\n"
                 for _, idx, fname in lst:
-                    self.add_info("  {:5}) {}\n".format(idx + 1, 
+                    self.add_info(fmt.format(idx + 1, 
                         self.fmt_hl_file(fname)))
             
         self.switch_view(0)
@@ -2166,9 +2195,10 @@ class App(tkinter.Frame):
                         k = fn.lower().replace("\\", "/")
                     lst.append((k, idx, fn))
                 lst.sort()
+                fmt = "  " + fmt_dec(len(lst)) + ") {}\n"
                 for _, idx, fn in lst:
                     #stid, _, _ = self.strfm.strtable[fn]
-                    self.add_info("  {:5}) {}\n".format(
+                    self.add_info(fmt.format(
                         idx + 1, self.fmt_hl_file(fn)))
             else:
                 try:


Commit: 2be7275bd5e795de56a1480d2d88d4e634b8a528
    https://github.com/scummvm/scummvm-tools/commit/2be7275bd5e795de56a1480d2d88d4e634b8a528
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fix tab for opcodes

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 7e43ec3a5..da8a71dcd 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1417,10 +1417,10 @@ class App(tkinter.Frame):
                             act_ref = "0x{:X}".format(act.act_ref)
                     msg += " 0x{:02X} {}".format(act.act_status, act_ref)
                 self.add_info(fmtra.format(idx, msg, len(act.ops), cmt))
-                fmtao = "    " + fmt_dec(len(act.ops)) + " {} "
+                fmtao = "    " + fmt_dec(len(act.ops)) + ")"
                 for oidx, op in enumerate(act.ops):
-                    self.add_info(fmtao.format(oidx, 
-                        self.fmt_opcode(op.op_code)))
+                    self.add_info(self.fmt_cmt(fmtao.format(oidx)))
+                    self.add_info(" " + self.fmt_opcode(op.op_code) + " ")
                     cmt = ""
                     if op.op_ref == rec.idx:
                         self.add_info("THIS")


Commit: 56a2a6b1c18bbfb95f66f0acb626be4fe17fe74a
    https://github.com/scummvm/scummvm-tools/commit/56a2a6b1c18bbfb95f66f0acb626be4fe17fe74a
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: decode MSK, LEG, OFF. display MSK, LEG, OFF, FLC

Changed paths:
  A engines/petka/petka/imgleg.py
  A engines/petka/petka/imgmsk.py
    engines/petka/help/changes.txt
    engines/petka/p12explore.py
    engines/petka/petka/__init__.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 81f89ee8c..0832dc58d 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -16,6 +16,9 @@
 Переход от хранилища к первому файлу в общем списке
 Сортировка списка файлов в хранилище
 Отступ от правого края для больших списков выбирается автоматически
+Движок: декодирование файлов LEG, OFF, MSK
+Отображение информации из LEG, OFF, MSK, FLC
+Информация о текущем разделе отображается в заголовке окна
 
 2014-12-20 версия 0.3e
 ----------------------
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index da8a71dcd..71b714801 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -283,7 +283,7 @@ class App(tkinter.Frame):
         
         # bind path handlers
         self.path_handler["parts"] = [self.path_parts, self.desc_parts]
-        self.path_handler["res"] = [self.path_res, None] # TODO:
+        self.path_handler["res"] = [self.path_res, self.desc_res]
         self.path_handler["objs"] = [self.path_objs_scenes,
             desc_def("Objects", "Object")]
         self.path_handler["scenes"] = [self.path_objs_scenes,
@@ -315,7 +315,6 @@ class App(tkinter.Frame):
 
         self.update_after()
         repath = "/about"
-        print(self.start_act)
         for cmd, arg in self.start_act:
             if cmd == "load":
                 if not self.open_data_from(arg):
@@ -337,7 +336,6 @@ class App(tkinter.Frame):
                     print("DEBUG: stop opening after " + arg)
                     repath = ""
                     break
-        print("Loading end", repath)
         if repath:
             self.open_path(repath)
 
@@ -475,6 +473,16 @@ class App(tkinter.Frame):
             self.hist.append([path])
             self.histf = []
         print("DEBUG: Open", path)
+        # set title
+        capt = APPNAME
+        try:
+            if path == ("about",):
+                capt = APPNAME + " - " + VERSION
+            else:
+                capt = APPNAME + " - " + self.desc_path(path)
+        except:
+            pass
+        self.master.title(capt)
         self.curr_path = path
         if len(path) > 0:
             self.curr_help = path[0]
@@ -1097,6 +1105,18 @@ class App(tkinter.Frame):
                 return False
         return True
         
+    def desc_res(self, path):
+        if path == ("res",):
+            path = ("res", "all")
+        if path[1] == "flt":
+            if len(path) > 2:
+                return "Resource type {}".format(path[2])
+            return "Resources by type"
+        if path[1] == "all":
+            if len(path) > 2:
+                return "Resource {}".format(path[2])
+            return "Resources"
+        return self.path_default(path)
 
     def path_res(self, path):
         # res - full list
@@ -1839,7 +1859,7 @@ class App(tkinter.Frame):
         self.switch_view(0)
         keys = None
         def keyslist():
-            opstat = {} # opcpdes count
+            opstat = {} # opcodes count
             acstat = {} # handlers count
             dastat = {} # dialog handlers count
             keys = list(petka.OPCODES.keys())
@@ -2293,6 +2313,49 @@ class App(tkinter.Frame):
                         self.add_info("  <a href=\"{}\">{}</a>\n".format(p, 
                             self.desc_path(p)))
                         
+                if fnl[-4:] in [".leg", ".off"]:
+                    legf = petka.LEGLoader()
+                    legf.load_data(self.strfm.read_file_stream(fnl))
+                    self.add_info("\n<b>LEG/OFF data</b>: {} frame(s)\n".format(
+                        len(legf.coords)))
+                    fmt = "  " + fmt_dec(len(legf.coords)) + ") {}, {}\n"
+                    for idx, (x, y) in enumerate(legf.coords):
+                        self.add_info(fmt.format(idx + 1, x, y))
+                        
+                if fnl[-4:] == ".msk":
+                    mskf = petka.MSKLoader()
+                    mskf.load_data(self.strfm.read_file_stream(fnl))
+                    self.add_info("\n<b>MSK data</b>: {} record(s)\n".format(
+                        len(mskf.rects)))
+                    self.add_info("  bound: {}, {} - {}, {}\n".format(
+                        *mskf.bound))
+                    fmt = "  " + fmt_dec(len(mskf.rects)) + \
+                        ") stamp = {}, {} rect(s)\n"
+                    for idx, (stamp, rs) in enumerate(mskf.rects):
+                        self.add_info(fmt.format(idx + 1, stamp, len(rs)))
+                        fmtr = "    " + fmt_dec(len(rs)) + ") {}, {} - {}, {}\n"
+                        for idxr, r in enumerate(rs):
+                            self.add_info(fmtr.format(idxr + 1, *r))
+                        
+                if fnl[-4:] == ".flc":
+                    flcf = petka.FLCLoader()
+                    flcf.load_info(self.strfm.read_file_stream(fnl))
+                    if flcf.image:
+                        # PIL
+                        self.add_info("\n<b>FLC data</b> (pil)\n")
+                        self.add_info("  Mode:   {}\n  Size:   {}x{}\n"
+                            "  Frames: {}\n  Delay:  {}".\
+                            format(flcf.image.mode, \
+                                flcf.image.size[0], flcf.image.size[1],
+                                flcf.frame_num, flcf.image.info["duration"]))
+                    else:    
+                        self.add_info("\n<b>FLC data</b> (internal)\n")
+                        self.add_info("  Mode:   P\n  Size:   {}x{}\n"\
+                            "  Frames: {}\nDelay: {}".\
+                            format(flcf.width, flcf.height, \
+                                flcf.frame_num, flcf.delay))
+                    
+                        
         self.switch_view(0)
         keys = None
         if self.last_path[:1] != ("files",):
diff --git a/engines/petka/petka/__init__.py b/engines/petka/petka/__init__.py
index 40b354c6f..e48578003 100644
--- a/engines/petka/petka/__init__.py
+++ b/engines/petka/petka/__init__.py
@@ -5,3 +5,6 @@ from .engine import Engine, OPCODES, DLGOPS
 from .fman import FileManager
 from .imgbmp import BMPLoader
 from .imgflc import FLCLoader
+from .imgleg import LEGLoader
+from .imgmsk import MSKLoader
+
diff --git a/engines/petka/petka/imgleg.py b/engines/petka/petka/imgleg.py
new file mode 100644
index 000000000..cc6c9398e
--- /dev/null
+++ b/engines/petka/petka/imgleg.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+# romiq.kh at gmail.com, 2014
+
+import array, struct, io
+
+from . import EngineError
+
+class LEGLoader:
+    def __init__(self):
+        self.coords = []
+        
+    def load_info(self, f):
+        return self.load_data(f)
+        
+    def load_data(self, f):
+        hdr = f.read(4)
+        if hdr != b"xyof":
+            raise EngineError("Bad LEG/OFF magic \"{}\"".format(hdr))
+
+        rest = f.read()
+        if len(rest) % 8:
+            raise EngineError("Bad LEG/OFF size {}".format(len(rest)))
+            
+        sf = struct.unpack("<{}l".format(len(rest) // 4), rest)
+        self.coords = [[sf[i * 2], sf[i * 2 + 1]] for i in range(len(rest) // 8)]
+
diff --git a/engines/petka/petka/imgmsk.py b/engines/petka/petka/imgmsk.py
new file mode 100644
index 000000000..cfd20dd9f
--- /dev/null
+++ b/engines/petka/petka/imgmsk.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+# romiq.kh at gmail.com, 2014
+
+import array, struct, io
+
+from . import EngineError
+
+class MSKLoader:
+    def __init__(self):
+        self.bound = [0, 0, 0, 0]
+        self.rects = []
+        
+    def load_info(self, f):
+        return self.load_data(f)
+        
+    def load_data(self, f):
+        rest = f.read()
+        f.seek(0)
+        delta = len(rest) - 16
+        rects = []
+        while delta > 0:
+            temp = f.read(4)
+            delta -= 4
+            rects_len = struct.unpack_from("<I", temp)[0]
+            rec = []
+            for r_ref in range(rects_len):
+                temp = f.read(8)
+                delta -= 8
+                l,t,r,b = struct.unpack_from("<4h", temp)
+                rec.append([l, t, r, b])
+            rects.append(rec)
+            delta -= 4
+            
+        if delta != 0:
+           raise EngineError("Bad MSK file")
+            
+        temp = f.read(len(rects) * 4)
+        frms = struct.unpack_from("<{}I".format(len(rects)), temp)
+
+        if len(rects) != len(frms):
+            raise EngineError("Bad MSK file structure")
+
+        temp = f.read(16)
+        self.bound = struct.unpack_from("<4i", temp)
+
+        self.rects = list(zip(reversed(frms), rects))
+


Commit: 36514cf080b30c772dac60a0960c0a06bcd6ed48
    https://github.com/scummvm/scummvm-tools/commit/36514cf080b30c772dac60a0960c0a06bcd6ed48
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fix doc

Changed paths:
    engines/petka/help/changes.txt


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 0832dc58d..1c796036a 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,6 +1,6 @@
 Что нового
 ==========
-2014-12-27 версия 0.3f
+2014-12-24 версия 0.3f
 ----------------------
 Сортировка сообщений по имени файла, по порядку, по тексту
 Информация о связанных файлах (FLC, MSG, LEF, OFF и BMP, CVX)


Commit: 040c526b50ce3eb29ecf2becc30c88a370d6f1ae
    https://github.com/scummvm/scummvm-tools/commit/040c526b50ce3eb29ecf2becc30c88a370d6f1ae
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fix doc, fix other

Changed paths:
  A engines/petka/help/dlgops.txt
  A engines/petka/help/opcodes.txt
    engines/petka/help/casts.txt
    engines/petka/help/changes.txt
    engines/petka/help/dlgs.txt
    engines/petka/help/files.txt
    engines/petka/help/index.txt
    engines/petka/help/info.txt
    engines/petka/help/invntr.txt
    engines/petka/help/list
    engines/petka/help/msgs.txt
    engines/petka/help/names.txt
    engines/petka/help/objs.txt
    engines/petka/help/parts.txt
    engines/petka/help/res_view.txt
    engines/petka/help/scenes.txt
    engines/petka/help/strs.txt
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/help/casts.txt b/engines/petka/help/casts.txt
index eade4010a..defa5742e 100644
--- a/engines/petka/help/casts.txt
+++ b/engines/petka/help/casts.txt
@@ -2,4 +2,7 @@
 
 На этой странице можно просмотреть цвета описаний предметов на экране.
 
+Дополнительно:
+
+ * <a href="/help/objs">Объекты</a>
 
diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 1c796036a..e3c475d1e 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,5 +1,12 @@
 Что нового
 ==========
+2014-12-27 версия 0.3g
+----------------------
+Исправлено отображение в списке фраз (сообщений)
+Добавлены справочные файлы о опкодах (объектов и  диалогов)
+Движок: улучшена загрузка иформации о подробностях сцен (перспектива, переходы)
+Отображение информации о перспективе сцены
+
 2014-12-24 версия 0.3f
 ----------------------
 Сортировка сообщений по имени файла, по порядку, по тексту
diff --git a/engines/petka/help/dlgops.txt b/engines/petka/help/dlgops.txt
new file mode 100644
index 000000000..cad8a9c24
--- /dev/null
+++ b/engines/petka/help/dlgops.txt
@@ -0,0 +1,13 @@
+Байт-код диалогов
+
+На этой странице можно статистику использования дилоговых опкодов используемых 
+группами дилогов в текущей загруженной части.
+
+Дополнительно:
+
+ * <a href="/help/comp_dlg">Синтаксис компиляции DIALOGUE.FIX</a>
+ * <a href="/help/info">Справочники</a>
+ * <a href="/help/msgs">Собщения</a>
+ * <a href="/help/dlgs">Группы диалогов</a>
+
+
diff --git a/engines/petka/help/dlgs.txt b/engines/petka/help/dlgs.txt
index e95d96cde..7b166b2ce 100644
--- a/engines/petka/help/dlgs.txt
+++ b/engines/petka/help/dlgs.txt
@@ -2,4 +2,10 @@
 
 На этой странице можно просмотреть все используемые группы диалогов.
 
+Дополнительно:
+
+ * <a href="/help/msgs">Собщения</a>
+ * <a href="/help/objs">Объекты</a>
+ * <a href="/help/scenes">Сцены</a>
+ * <a href="/help/dlgops">Байт-код дилогов</a>
 
diff --git a/engines/petka/help/files.txt b/engines/petka/help/files.txt
index ab776caf5..996400360 100644
--- a/engines/petka/help/files.txt
+++ b/engines/petka/help/files.txt
@@ -2,4 +2,8 @@
 
 На этой странице можно просмотреть общий список загруженных файлов.
 
+Дополнительно:
+
+ * <a href="/help/strs">Хранилища</a>
+ * <a href="/help/res">Ресурсы</a>
 
diff --git a/engines/petka/help/index.txt b/engines/petka/help/index.txt
index 9ba1946ba..bc5de66e5 100644
--- a/engines/petka/help/index.txt
+++ b/engines/petka/help/index.txt
@@ -27,6 +27,8 @@
  * <a href="/help/casts">Цвета предметов</a>
  * <a href="/help/msgs">Собщения</a>
  * <a href="/help/dlgs">Группы диалогов</a>
+ * <a href="/help/opcodes">Байт-код</a>
+ * <a href="/help/dlgops">Байт-код дилогов</a>
  * <a href="/help/strs">Хранилища</a>
  * <a href="/help/files">Файлы</a>
  
diff --git a/engines/petka/help/info.txt b/engines/petka/help/info.txt
index 51ea6c817..553bcb46e 100644
--- a/engines/petka/help/info.txt
+++ b/engines/petka/help/info.txt
@@ -5,3 +5,10 @@
  * Коды операций в обработчиках
  * Коды операций в диалогах
 
+Дополнительно:
+
+ * <a href="/help/objs">Объекты</a>
+ * <a href="/help/scenes">Сцены</a>
+ * <a href="/help/opcodes">Байт-код</a>
+ * <a href="/help/dlgops">Байт-код дилогов</a>
+
diff --git a/engines/petka/help/invntr.txt b/engines/petka/help/invntr.txt
index 7bfdd3be9..81190eb8c 100644
--- a/engines/petka/help/invntr.txt
+++ b/engines/petka/help/invntr.txt
@@ -2,4 +2,7 @@
 
 На этой странице можно просмотреть описание предметов инвентаря.
 
+Дополнительно:
+
+ * <a href="/help/objs">Объекты</a>
 
diff --git a/engines/petka/help/list b/engines/petka/help/list
index 332e97473..ddca09a42 100644
--- a/engines/petka/help/list
+++ b/engines/petka/help/list
@@ -11,6 +11,8 @@ invntr
 casts
 msgs
 dlgs
+opcodes
+dlgops
 strs
 files
 translate
diff --git a/engines/petka/help/msgs.txt b/engines/petka/help/msgs.txt
index 2221caceb..6264e39bd 100644
--- a/engines/petka/help/msgs.txt
+++ b/engines/petka/help/msgs.txt
@@ -2,4 +2,8 @@
 
 На этой странице можно просмотреть все используемые сообщения.
 
+Дополнительно:
+
+ * <a href="/help/dlgs">Группы диалогов</a>
+ * <a href="/help/objs">Объекты</a>
 
diff --git a/engines/petka/help/names.txt b/engines/petka/help/names.txt
index 11331ac02..e418fd51c 100644
--- a/engines/petka/help/names.txt
+++ b/engines/petka/help/names.txt
@@ -2,4 +2,7 @@
 
 На этой странице можно просмотреть используемые отображаемые имена.
 
+Дополнительно:
+
+ * <a href="/help/objs">Объекты</a>
 
diff --git a/engines/petka/help/objs.txt b/engines/petka/help/objs.txt
index 3a4f380f7..7f0e40562 100644
--- a/engines/petka/help/objs.txt
+++ b/engines/petka/help/objs.txt
@@ -2,4 +2,11 @@
 
 На этой странице можно просмотреть используемые объекты и их параметры.
 
+Дополнительно:
+
+ * <a href="/help/scenes">Сцены</a>
+ * <a href="/help/res">Ресурсы</a>
+ * <a href="/help/names">Отображаемные имена</a>
+ * <a href="/help/invntr">Инвентарь</a>
+ * <a href="/help/casts">Цвета предметов</a>
 
diff --git a/engines/petka/help/opcodes.txt b/engines/petka/help/opcodes.txt
new file mode 100644
index 000000000..f4ae9ef73
--- /dev/null
+++ b/engines/petka/help/opcodes.txt
@@ -0,0 +1,12 @@
+Байт-код
+
+На этой странице можно статистику использования опкодов используемых объектами
+и сценами в текущей загруженной части.
+
+Дополнительно:
+
+ * <a href="/help/comp_scr">Синтаксис компиляции SCRIPT.DAT</a>
+ * <a href="/help/info">Справочники</a>
+ * <a href="/help/objs">Объекты</a>
+ * <a href="/help/scenes">Сцены</a>
+
diff --git a/engines/petka/help/parts.txt b/engines/petka/help/parts.txt
index 0554a03d0..10e6bcd33 100644
--- a/engines/petka/help/parts.txt
+++ b/engines/petka/help/parts.txt
@@ -3,4 +3,19 @@
 На этой странице можно выбрать часть с которой будет в дальнейшем вестись 
 работа.
 
+Дополнительно:
+
+ * <a href="/help/objs">Объекты</a>
+ * <a href="/help/res">Ресурсы</a>
+ * <a href="/help/objs">Объекты</a>
+ * <a href="/help/scenes">Сцены</a>
+ * <a href="/help/names">Отображаемные имена</a>
+ * <a href="/help/invntr">Инвентарь</a>
+ * <a href="/help/casts">Цвета предметов</a>
+ * <a href="/help/msgs">Собщения</a>
+ * <a href="/help/dlgs">Группы диалогов</a>
+ * <a href="/help/opcodes">Байт-код</a>
+ * <a href="/help/dlgops">Байт-код дилогов</a>
+ * <a href="/help/strs">Хранилища</a>
+ * <a href="/help/files">Файлы</a>
 
diff --git a/engines/petka/help/res_view.txt b/engines/petka/help/res_view.txt
index b0ff49f59..f0d6b3395 100644
--- a/engines/petka/help/res_view.txt
+++ b/engines/petka/help/res_view.txt
@@ -1,3 +1,9 @@
 Подробная информация о ресурсе
 
 На этой странице можно просмотреть подробную информацию о ресурсе.
+
+Дополнительно:
+
+ * <a href="/help/res">Ресурсы</a>
+ * <a href="/help/files">Файлы</a>
+
diff --git a/engines/petka/help/scenes.txt b/engines/petka/help/scenes.txt
index 31e7e9302..58767ea2d 100644
--- a/engines/petka/help/scenes.txt
+++ b/engines/petka/help/scenes.txt
@@ -2,4 +2,8 @@
 
 На этой странице можно просмотреть используемые сцены и их параметры.
 
+Дополнительно:
+
+ * <a href="/help/objs">Объекты</a>
+ * <a href="/help/res">Ресурсы</a>
 
diff --git a/engines/petka/help/strs.txt b/engines/petka/help/strs.txt
index eca1839f6..c00b3cdc5 100644
--- a/engines/petka/help/strs.txt
+++ b/engines/petka/help/strs.txt
@@ -2,4 +2,8 @@
 
 На этой странице можно просмотреть все загруженные хранилища и их содержимое.
 
+Дополнительно:
+
+ * <a href="/help/files">Файлы</a>
+ * <a href="/help/res">Ресурсы</a>
 
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 71b714801..473d8df4c 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -30,7 +30,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.3e 2014-12-20"
+VERSION = "v0.3g 2014-12-27"
 
 def hlesc(value):
     if value is None:
@@ -43,6 +43,9 @@ def cesc(value):
 def fmt_hl(loc, desc):
     return "<a href=\"{}\">{}</a>".format(loc, desc)
 
+def fmt_hl_len(loc, desc, ln):
+    sz = max(ln - len(desc), 0)
+    return " "*sz + "<a href=\"{}\">{}</a>".format(loc, desc)
 
 def fmt_arg(value):
     if value < 10:
@@ -1509,6 +1512,11 @@ class App(tkinter.Frame):
                       " #{}".format(hid) + self.fmt_cmt(" // " + 
                       self.fmt_hl_obj_scene(oid, True)) + "\n")
 
+            # perspective
+            if not isobj and rec.persp:
+                self.add_info("\n<b>Perspective</b>:\n  {}, {}, {}, {}, {}\n".
+                    format(*rec.persp))
+                
             # enter areas
             if not isobj and rec.entareas:
                 self.add_info("\n<b>Enter areas</b>: {}\n".format(
@@ -1516,6 +1524,8 @@ class App(tkinter.Frame):
                 for sf, oo in rec.entareas:
                     self.add_info("  <i>from</i>: {}\n".format(
                         self.fmt_hl_scene(sf.idx, True)))
+                    # 
+                    print(rec.name)
                     self.add_info("    <i>on</i>: {}\n".format(
                         self.fmt_hl_obj(oo.idx, True)))
         return True
@@ -1657,10 +1667,13 @@ class App(tkinter.Frame):
                         k = msg.name
                     lst.append((k, msg.msg_wav, idx, msg.name))
                 lst.sort()
+                fmtlen = fmt_dec_len(len(lst))
                 for _, wav, idx, capt in lst:
-                    self.add_info("  <a href=\"/msgs/{}\">{}</a> - {} - {}\n".
-                        format(idx, idx, self.fmt_hl_file("speech{}/{}".format(
-                            self.sim.curr_part, wav), wav), capt))
+                    self.add_info("  " + fmt_hl_len("/msgs/{}".format(idx), 
+                        "{}".format(idx), fmtlen))
+                    self.add_info(" - {} - {}\n".format(
+                        self.fmt_hl_file("speech{}/{}".format(
+                        self.sim.curr_part, wav), wav), capt))
             else:
                 # msg info
                 self.add_info("<b>Message</b>: {}\n".format(path[1]))
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index b5a9685f0..5c87945c4 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -277,25 +277,25 @@ class Engine:
         # load .STR
         strs = ["Flics", "Background", "Wav", "Music", "SFX", "Speech"]
         for strf in strs:
-            #pf = self.fman.find_path(self.curr_path + "bgs.ini")
-            #if not pf: continue
             if strf in ini:
                 self.fman.load_store(ini[strf], 1)
-        # load script.dat, backgrnd.bg and resources.qrc
+        # load script.dat, backgrnd.bg, resources.qrc, etc
         self.load_script()
-        # parse enter areas
+        # bgs.ini: parse enter areas and perspective
+        settings = self.bgs_ini.get("Settings", {})
         for scene in self.scenes:
             scene.entareas = None
+            areas = self.bgs_ini.get(scene.name, {})
             if scene.name in self.bgs_ini:
                 scene.entareas = []
-                for key in self.bgs_ini["__order__"][scene.name]:
+                for key in areas.keys():
                     # search scene
                     sf = None
                     for scenefrom in self.scenes:
                         if scenefrom.name == key:
                             sf = scenefrom
                             break
-                    value = self.bgs_ini[scene.name][key]
+                    value = areas[key]
                     # search objects
                     oo = None
                     for objon in self.objects:
@@ -304,6 +304,16 @@ class Engine:
                             break
                     if sf and oo:
                         scene.entareas.append((sf, oo))
+            # persp
+            persp = settings.get(scene.name, "")
+            persp = persp.split(" ")
+            persp = [x for x in persp if x]
+            try:
+                persp = [float(persp[0]), float(persp[1]),
+                    int(persp[2]), int(persp[3]), float(persp[4])]
+            except:
+                persp = None
+            scene.persp = persp
         # load names & invntr
         self.load_names()
         # load dialogs


Commit: 7c2bb9349c7079d9bcade44cf8256b0fb1fb64bd
    https://github.com/scummvm/scummvm-tools/commit/7c2bb9349c7079d9bcade44cf8256b0fb1fb64bd
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: enchance

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 473d8df4c..3b5b92194 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -45,7 +45,7 @@ def fmt_hl(loc, desc):
 
 def fmt_hl_len(loc, desc, ln):
     sz = max(ln - len(desc), 0)
-    return " "*sz + "<a href=\"{}\">{}</a>".format(loc, desc)
+    return " "*sz + fmt_hl(loc, desc)
 
 def fmt_arg(value):
     if value < 10:
@@ -931,26 +931,26 @@ class App(tkinter.Frame):
         if self.sim:
             self.add_info("Current part {} chapter {}\n\n".\
                     format(self.sim.curr_part, self.sim.curr_chap))
-            self.add_info("  Resources:     <a href=\"/res\">{}</a>\n".\
-                format(len(self.sim.res)))
-            self.add_info("  Objects:       <a href=\"/objs\">{}</a>\n".\
-                format(len(self.sim.objects)))
-            self.add_info("  Scenes:        <a href=\"/scenes\">{}</a>\n".\
-                format(len(self.sim.scenes)))
-            self.add_info("  Names:         <a href=\"/names\">{}</a>\n".\
-                format(len(self.sim.names)))
-            self.add_info("  Invntr:        <a href=\"/invntr\">{}</a>\n".\
-                format(len(self.sim.invntr)))
-            self.add_info("  Casts:         <a href=\"/casts\">{}</a>\n".\
-                format(len(self.sim.casts)))
-            self.add_info("  Messages       <a href=\"/msgs\">{}</a>\n".\
-                format(len(self.sim.msgs)))
-            self.add_info("  Dialog groups: <a href=\"/dlgs\">{}</a>\n".\
-                format(len(self.sim.dlgs)))
-            self.add_info("  Opened stores: <a href=\"/strs\">{}</a>\n".
-                format(len(self.strfm.strfd)))
-            self.add_info("  Files:         <a href=\"/files\">{}</a>\n".
-                format(len(self.strfm.strtable)))
+            self.add_info("  Resources:     " + fmt_hl("/res", 
+                len(self.sim.res)) + "\n")
+            self.add_info("  Objects:       " + fmt_hl("/objs", 
+                len(self.sim.objects)) + "\n")
+            self.add_info("  Scenes:        " + fmt_hl("/scenes", 
+                len(self.sim.scenes)) + "\n")
+            self.add_info("  Names:         " + fmt_hl("/names", 
+                len(self.sim.names)) + "\n")
+            self.add_info("  Invntr:        " + fmt_hl("/invntr", 
+                len(self.sim.invntr)) + "\n")
+            self.add_info("  Casts:         " + fmt_hl("/casts", 
+                len(self.sim.casts)) + "\n")
+            self.add_info("  Messages       " + fmt_hl("/msgs", 
+                len(self.sim.msgs)) + "\n")
+            self.add_info("  Dialog groups: " + fmt_hl("/dlgs", 
+                len(self.sim.dlgs)) + "\n")
+            self.add_info("  Opened stores: " + fmt_hl("strs", 
+                len(self.strfm.strfd)) + "\n")
+            self.add_info("  Files:         " + fmt_hl("/files", 
+                len(self.strfm.strtable)) + "\n")
             scn = hlesc(self.sim.start_scene)
             for scene in self.sim.scenes:
                 if scene.name == self.sim.start_scene:
@@ -958,15 +958,14 @@ class App(tkinter.Frame):
                     break
             self.add_info("  Start scene:   {}\n".format(scn))
             self.add_info("\n")
-            self.add_info("  <a href=\"/opcodes\">Opcodes</a>\n")
-            self.add_info("  <a href=\"/dlgops\">Dialog opcodes</a>\n\n")
-
+            self.add_info("  " + fmt_hl("/opcodes", "Opcodes") + "\n")
+            self.add_info("  " + fmt_hl("/dlgops", "Dialog opcodes") + "\n")
         elif self.strfm:
             self.add_info("Single store mode\n\n")
-            self.add_info("  Opened stores: <a href=\"/strs\">{}</a>\n".
-                format(len(self.strfm.strfd)))
-            self.add_info("  Files:         <a href=\"/files\">{}</a>\n".
-                format(len(self.strfm.strtable)))
+            self.add_info("  Opened stores: " + fmt_hl("/strs", 
+                len(self.strfm.strfd)) + "\n")
+            self.add_info("  Files:         " + fmt_hl("/files", 
+                len(self.strfm.strtable)) + "\n")
 
     def show_hist(self):
         self.switch_view(0)
@@ -1255,8 +1254,8 @@ class App(tkinter.Frame):
     def path_res_status(self):
         self.switch_view(0)
         self.clear_info()
-        self.add_info("<b>Resources</b>: <a href=\"/res\">{}</a>\n"\
-            "Filetypes:\n".format(len(self.sim.res)))
+        self.add_info("<b>Resources</b>: " + fmt_hl("/res", len(self.sim.res)) 
+            + "\nFiletypes:\n")
         fts = {}
         for res in self.sim.res.values():
             fp = res.rfind(".")
@@ -2323,8 +2322,8 @@ class App(tkinter.Frame):
                 if sa:
                     self.add_info("\n<b>See also</b>:\n")
                     for p in sa:
-                        self.add_info("  <a href=\"{}\">{}</a>\n".format(p, 
-                            self.desc_path(p)))
+                        self.add_info("  " + fmt_hl(p, self.desc_path(p)) 
+                            + "\n")
                         
                 if fnl[-4:] in [".leg", ".off"]:
                     legf = petka.LEGLoader()


Commit: 08e06d907b6092e30db0a6681d938bcdc9e32d33
    https://github.com/scummvm/scummvm-tools/commit/08e06d907b6092e30db0a6681d938bcdc9e32d33
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fixes

Changed paths:
    engines/petka/help/dlgops.txt
    engines/petka/help/opcodes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/dlgops.txt b/engines/petka/help/dlgops.txt
index cad8a9c24..7d4f0981c 100644
--- a/engines/petka/help/dlgops.txt
+++ b/engines/petka/help/dlgops.txt
@@ -3,6 +3,8 @@
 На этой странице можно статистику использования дилоговых опкодов используемых 
 группами дилогов в текущей загруженной части.
 
+Цифры - колличество использований опкодов во всех обработчиках далогов.
+
 Дополнительно:
 
  * <a href="/help/comp_dlg">Синтаксис компиляции DIALOGUE.FIX</a>
diff --git a/engines/petka/help/opcodes.txt b/engines/petka/help/opcodes.txt
index f4ae9ef73..bcf4884d9 100644
--- a/engines/petka/help/opcodes.txt
+++ b/engines/petka/help/opcodes.txt
@@ -3,6 +3,11 @@
 На этой странице можно статистику использования опкодов используемых объектами
 и сценами в текущей загруженной части.
 
+Первая колонка - количество использований этого опкода в обработчике.
+Вторая колонка - количество обработчиков такого типа у объектов и сцен.
+Третья колонка - количество обработчиков такого типа у диалогов.
+
+
 Дополнительно:
 
  * <a href="/help/comp_scr">Синтаксис компиляции SCRIPT.DAT</a>
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 3b5b92194..7706c94ee 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1136,7 +1136,7 @@ class App(tkinter.Frame):
             return self.path_default(path)
 
     def path_res_open(self, pref, res_id, mode):
-        self.curr_help = "res_view"
+        self.curr_help = "res_view" # help override 
         if res_id not in self.sim.res:        
             self.switch_view(0)
             self.clear_info()
@@ -1523,8 +1523,6 @@ class App(tkinter.Frame):
                 for sf, oo in rec.entareas:
                     self.add_info("  <i>from</i>: {}\n".format(
                         self.fmt_hl_scene(sf.idx, True)))
-                    # 
-                    print(rec.name)
                     self.add_info("    <i>on</i>: {}\n".format(
                         self.fmt_hl_obj(oo.idx, True)))
         return True


Commit: 78fd00b49c86e05e0bf6058012411721811a3183
    https://github.com/scummvm/scummvm-tools/commit/78fd00b49c86e05e0bf6058012411721811a3183
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: canv resize

Changed paths:
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 7706c94ee..8cde05189 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -656,7 +656,10 @@ class App(tkinter.Frame):
     def switch_view(self, main):
         # main view
         if main == self.curr_main: return
+        last = self.curr_main
         self.curr_main = main
+        rw = None
+        rh = None
         if main == 0:
             self.canv_view.delete(tkinter.ALL)
             self.canv_view.grid_forget()
@@ -669,6 +672,10 @@ class App(tkinter.Frame):
             self.scr_view_x.config(command = self.text_view.xview)
             self.scr_view_y.config(command = self.text_view.yview)
         else:
+        
+            if last == 0:
+                rw = self.text_view.winfo_width()
+                rh = self.text_view.winfo_height()
             self.canv_view.delete(tkinter.ALL)
             self.text_view.grid_forget()
             self.canv_view.grid(row = 0, column = 0, \
@@ -679,6 +686,10 @@ class App(tkinter.Frame):
             )
             self.scr_view_x.config(command = self.canv_view.xview)
             self.scr_view_y.config(command = self.canv_view.yview)
+            if rh:
+                print(rh)
+                self.canv_view.height = rh
+                print(self.canv_view.winfo_height())
 
     def clear_info(self):
         self.text_view.delete(0.0, tkinter.END)


Commit: 56d009d9d561e98142d2de0ebe847268cbe11ab8
    https://github.com/scummvm/scummvm-tools/commit/56d009d9d561e98142d2de0ebe847268cbe11ab8
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: refactor bgs loading

Changed paths:
    engines/petka/petka/engine.py


diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 5c87945c4..87488c6ac 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -261,19 +261,6 @@ class Engine:
             self.curr_speech = ""
             self.curr_diskid = None
         
-        # load BGS.INI
-        self.bgs_ini = {}
-        self.start_scene = None
-        bgsfn = self.curr_path + "bgs.ini"
-        if self.fman.exists(bgsfn):
-            f = self.fman.read_file_stream(bgsfn)
-            try:
-                self.bgs_ini = self.parse_ini(f)
-            finally:
-                f.close()
-            if "Settings" in self.bgs_ini:
-                if "StartRoom" in self.bgs_ini["Settings"]:
-                    self.start_scene = self.bgs_ini["Settings"]["StartRoom"]
         # load .STR
         strs = ["Flics", "Background", "Wav", "Music", "SFX", "Speech"]
         for strf in strs:
@@ -281,39 +268,8 @@ class Engine:
                 self.fman.load_store(ini[strf], 1)
         # load script.dat, backgrnd.bg, resources.qrc, etc
         self.load_script()
-        # bgs.ini: parse enter areas and perspective
-        settings = self.bgs_ini.get("Settings", {})
-        for scene in self.scenes:
-            scene.entareas = None
-            areas = self.bgs_ini.get(scene.name, {})
-            if scene.name in self.bgs_ini:
-                scene.entareas = []
-                for key in areas.keys():
-                    # search scene
-                    sf = None
-                    for scenefrom in self.scenes:
-                        if scenefrom.name == key:
-                            sf = scenefrom
-                            break
-                    value = areas[key]
-                    # search objects
-                    oo = None
-                    for objon in self.objects:
-                        if objon.name == value:
-                            oo = objon
-                            break
-                    if sf and oo:
-                        scene.entareas.append((sf, oo))
-            # persp
-            persp = settings.get(scene.name, "")
-            persp = persp.split(" ")
-            persp = [x for x in persp if x]
-            try:
-                persp = [float(persp[0]), float(persp[1]),
-                    int(persp[2]), int(persp[3]), float(persp[4])]
-            except:
-                persp = None
-            scene.persp = persp
+        # load persp & scenes enter points
+        self.load_bgs()
         # load names & invntr
         self.load_names()
         # load dialogs
@@ -480,6 +436,55 @@ class Engine:
                     r, g, b = 255, 255, 255
                 obj.cast = (r, g, b)
             
+    def load_bgs(self):
+        # load BGS.INI
+        self.bgs_ini = {}
+        self.start_scene = None
+        bgsfn = self.curr_path + "bgs.ini"
+        if self.fman.exists(bgsfn):
+            f = self.fman.read_file_stream(bgsfn)
+            try:
+                self.bgs_ini = self.parse_ini(f)
+            finally:
+                f.close()
+        
+        settings = self.bgs_ini.get("Settings", {})
+        self.start_scene = settings.get("StartRoom", None)
+
+        # bgs.ini: parse enter areas and perspective
+        for scene in self.scenes:
+            scene.entareas = None
+            areas = self.bgs_ini.get(scene.name, {})
+            if scene.name in self.bgs_ini:
+                scene.entareas = []
+                for key in areas.keys():
+                    # search scene
+                    sf = None
+                    for scenefrom in self.scenes:
+                        if scenefrom.name == key:
+                            sf = scenefrom
+                            break
+                    value = areas[key]
+                    # search objects
+                    oo = None
+                    for objon in self.objects:
+                        if objon.name == value:
+                            oo = objon
+                            break
+                    if sf and oo:
+                        scene.entareas.append((sf, oo))
+            # persp
+            persp = settings.get(scene.name, "")
+            persp = persp.split(" ")
+            persp = [x for x in persp if x]
+            try:
+                persp = [float(persp[0]), float(persp[1]),
+                    int(persp[2]), int(persp[3]), float(persp[4])]
+            except:
+                persp = None
+            scene.persp = persp
+    
+            
     def load_dialogs(self, fixname = None, lodname = None, noobjref = False):
         self.msgs = []
         # DIALOGUES.LOD


Commit: 17d4eba581536eaba14bf9650d090f0149aba8e0
    https://github.com/scummvm/scummvm-tools/commit/17d4eba581536eaba14bf9650d090f0149aba8e0
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: save decoding

Changed paths:
  A engines/petka/help/save.txt
  A engines/petka/petka/saves.py
    engines/petka/help/changes.txt
    engines/petka/help/cmdline.txt
    engines/petka/help/faq.txt
    engines/petka/help/index.txt
    engines/petka/help/list
    engines/petka/p12explore.py
    engines/petka/petka/__init__.py
    engines/petka/petka/engine.py
    engines/petka/petka/fman.py
    engines/petka/petka/imgbmp.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index e3c475d1e..e960e2f31 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -6,6 +6,8 @@
 Добавлены справочные файлы о опкодах (объектов и  диалогов)
 Движок: улучшена загрузка иформации о подробностях сцен (перспектива, переходы)
 Отображение информации о перспективе сцены
+Движок: загрузка сохранений (частично)
+Загрузка сохранений и отображение информации (частично)
 
 2014-12-24 версия 0.3f
 ----------------------
diff --git a/engines/petka/help/cmdline.txt b/engines/petka/help/cmdline.txt
index 52d7c8549..c552b87b1 100644
--- a/engines/petka/help/cmdline.txt
+++ b/engines/petka/help/cmdline.txt
@@ -6,11 +6,15 @@
 
   p12explore [-d путь к данным]|[-t путь к переводу]|[открываемый раздел]
 
+Путь к данным - путь к файлу или каталогу где находится файл с данными.
+
 Открыть хранилище
 
-  p12explore [-s путь к .str]
+  p12explore [-s путь к файлу .str]
 
-Путь к данным - путь к файлу или каталогу где находится файл с данными.
+Загрузить сохранённое состояние
+
+  p12explore [-sd путь к файлу save?.dat]
   
 Основные разделы:
 
@@ -20,9 +24,14 @@
  * /res/all/31000 - загрузить ресурс 31000
  * /objs - объекты
  * /scenes - сцены
+ * /names - отображаемые имена
+ * /invntr - инвентарь
+ * /casts - цвета предметов
  * /msgs - сообщения
  * /dlgs - группы диалогов
  * /strs - хранилища
+ * /files - загруженнве файлы
+ * /save - сохранённое состояние игры
  
 Так как после открытия данных данные о переводе удаляются то рекомендуется
   следующий порядок загрузки:
diff --git a/engines/petka/help/faq.txt b/engines/petka/help/faq.txt
index 1e30f7fe7..2b8d65773 100644
--- a/engines/petka/help/faq.txt
+++ b/engines/petka/help/faq.txt
@@ -7,3 +7,6 @@
   предназначена для Демо-версии 1й части. Тем не менее вы можете перейти в 
   раздел выбор части (Edit->Parts) и нажать кнопку [Fix Paths].
   
+<i>При открытии файла с сохранением (SAVEx.DAT) появляется ошибка</i>
+Убедитесь что файл с сохранением предназначен для этой игры.
+
diff --git a/engines/petka/help/index.txt b/engines/petka/help/index.txt
index bc5de66e5..9a2449a62 100644
--- a/engines/petka/help/index.txt
+++ b/engines/petka/help/index.txt
@@ -11,6 +11,7 @@
  * Просматривать и распаковывать содержимое .STR файлов
  * Работать с переводом игры на другой язык (см. <a href="/help/translate">для переводчика</a>)
  * Декомпилировать структуру и <a href="/help/compiler">компилировать</a> обратно
+ * Просматривать сохраненное состояние
  * <a href="/help/changes">Что нового</a>
  * <a href="/help/faq">Часто задаваемые вопросы</a>
 
@@ -31,7 +32,7 @@
  * <a href="/help/dlgops">Байт-код дилогов</a>
  * <a href="/help/strs">Хранилища</a>
  * <a href="/help/files">Файлы</a>
- 
+ * <a href="/help/save">Сохраненное состояние</a>
 
 Прочая информация
 
diff --git a/engines/petka/help/list b/engines/petka/help/list
index ddca09a42..1273fb9a1 100644
--- a/engines/petka/help/list
+++ b/engines/petka/help/list
@@ -15,6 +15,7 @@ opcodes
 dlgops
 strs
 files
+save
 translate
 cmdline
 support
diff --git a/engines/petka/help/save.txt b/engines/petka/help/save.txt
new file mode 100644
index 000000000..28beae819
--- /dev/null
+++ b/engines/petka/help/save.txt
@@ -0,0 +1,11 @@
+Сохраненное состояние
+
+На этой странице можно просмотреть содержимое сохранённого состояния.
+Для работы необходимо предварительно загрузить файл SAVEx.DAT.
+
+Дополнительно:
+
+ * <a href="/help/objs">Объекты</a>
+ * <a href="/help/scenes">Сцены</a>
+
+
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 8cde05189..642ddfae0 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -156,8 +156,7 @@ class HyperlinkManager:
             if tag[:6] == "hyper-":
                 self.links[tag]()
                 return
-		
-		
+
 # thanx http://tkinter.unpythonic.net/wiki/ReadOnlyText
 class ReadOnlyText(tkinter.Text):
     def __init__(self, *args, **kwargs):
@@ -167,7 +166,7 @@ class ReadOnlyText(tkinter.Text):
             self.redirector.register("insert", lambda *args, **kw: "break")
         self.delete = \
             self.redirector.register("delete", lambda *args, **kw: "break")
-		
+
 class App(tkinter.Frame):
     def __init__(self, master):
         tkinter.Frame.__init__(self, master)
@@ -207,9 +206,11 @@ class App(tkinter.Frame):
         self.sim = None
         # store manager
         self.strfm = None
+        # save
+        self.save = None
         # translation
-        self.tran = None   
-        
+        self.tran = None
+           
     def create_widgets(self):
         ttk.Style().configure("Tool.TButton", width = -1) # minimal width
         ttk.Style().configure("TLabel", padding = self.pad)
@@ -310,6 +311,7 @@ class App(tkinter.Frame):
         self.path_handler["strs"] = [self.path_stores,
             desc_def("Stores", "Store")]
         self.path_handler["files"] = [self.path_files, self.desc_files]
+        self.path_handler["save"] = [self.path_save, "Save"]
         self.path_handler["test"] = [self.path_test, "Tests"]
         self.path_handler["about"] = [self.path_about, "About"]
         self.path_handler["support"] = [self.path_support, "Support"]
@@ -329,6 +331,12 @@ class App(tkinter.Frame):
                     break
                 else:
                     repath = "/strs"
+            elif cmd == "savedat":
+                if not self.open_savedat_from(arg):
+                    repath = ""
+                    break
+                else:
+                    repath = "/save"
             elif cmd == "tran":
                 if not self.open_tran_from(arg):
                     repath = ""
@@ -363,6 +371,9 @@ class App(tkinter.Frame):
         self.menufile.add_command(
                 command = self.on_open_data,
                 label = "Open data...")
+        self.menufile.add_command(
+                command = self.on_open_savedat,
+                label = "Open SAVEx.DAT...")
         self.menufile.add_separator()
         self.menufile.add_command(
                 command = self.on_open_str,
@@ -378,7 +389,7 @@ class App(tkinter.Frame):
                 
         editnav = ["/parts", None, "/res", "/objs", "/scenes", "/names", 
             "/invntr", "/casts", "/msgs", "/dlgs", "/opcodes", "/dlgops", 
-            "/strs", "/files"]
+            "/strs", "/files", "/save"]
         mkmenupaths(self.menuedit, editnav)
         
         self.menunav = tkinter.Menu(self.master, tearoff = 0)
@@ -977,6 +988,8 @@ class App(tkinter.Frame):
                 len(self.strfm.strfd)) + "\n")
             self.add_info("  Files:         " + fmt_hl("/files", 
                 len(self.strfm.strtable)) + "\n")
+        if self.save:
+            self.add_info("  " + fmt_hl("/save", "Save") + "\n")
 
     def show_hist(self):
         self.switch_view(0)
@@ -1039,6 +1052,13 @@ class App(tkinter.Frame):
             ]
             for name, act in acts:
                 self.insert_lb_act(name, act)
+
+        if self.save is not None:
+            acts = [
+                ("Save", "/save"),
+            ]
+            for name, act in acts:
+                self.insert_lb_act(name, act)
         return True
 
     def desc_parts(self, path):
@@ -2375,7 +2395,6 @@ class App(tkinter.Frame):
                             "  Frames: {}\nDelay: {}".\
                             format(flcf.width, flcf.height, \
                                 flcf.frame_num, flcf.delay))
-                    
                         
         self.switch_view(0)
         keys = None
@@ -2390,6 +2409,43 @@ class App(tkinter.Frame):
                 "files.sort", {0: "order", 1: "filename"}, upd_files)
         upd_files()
         return True
+
+    def path_save(self, path):
+        if self.sim is None:
+            return self.path_default([])
+            
+        def upd_save():
+            path = self.curr_path
+            fid = None
+            self.switch_view(0)
+            if self.save is None:
+                self.add_info("<b>Saved state:not loaded</b>\n\n")
+                
+            if path == ("save",):
+                path = ("save", "info")
+            if path[1] == "shot":
+                self.select_lb_item("shot")
+                self.main_image = \
+                    self.make_image(self.save.shot)
+                self.switch_view(1)
+                self.update_canvas()
+            elif path[1] == "info":
+                self.clear_info()
+                self.add_info("<b>Saved state:</b>\n\n")
+                self.add_info("  part:  {}, chapter {}\n".format(
+                    self.save.part, self.save.chap))
+                self.add_info("  stamp: " + hlesc(self.save.stamp) + "\n")
+                self.add_info("  scene: " + hlesc(self.save.scene) + "\n")
+            else:
+                self.select_lb_item("info")
+                        
+        if self.last_path[:1] != ("save",):
+            # calc statistics
+            self.update_gui("Save")
+            self.insert_lb_act("Info", ["save"], "info")
+            self.insert_lb_act("Screenshot", ["save", "shot"], "shot")
+        upd_save()
+        return True
             
             
     def path_test(self, path):
@@ -2516,6 +2572,12 @@ class App(tkinter.Frame):
             self.add_info("  <b>Path</b>: {}\n\n".format(
                 hlesc(self.strfm.root)))
 
+        self.add_info("<b>Saved state</b>: ")
+        if not self.strfm:
+            self.add_info("<i>not loaded</i>\n")
+        else:
+            self.add_info("<i>loaded</i>\n\n")
+
         if self.sim or self.strfm:
             self.path_info_outline()
 
@@ -2667,7 +2729,7 @@ class App(tkinter.Frame):
         os.chdir(os.path.dirname(fn))
         self.clear_hist()
         if self.open_str_from(fn):
-            self.open_path("")
+            self.open_path("/strs")
             self.clear_hist()
 
     def open_str_from(self, fn):
@@ -2685,6 +2747,49 @@ class App(tkinter.Frame):
             self.clear_info()
             self.add_info("Error opening \"{}\" \n\n{}".\
                 format(hlesc(fn), hlesc(traceback.format_exc())))
+
+    def on_open_savedat(self):
+        ft = [\
+            ('Save files (*.dat)', '.DAT'),
+            ('all files', '.*')]
+        fn = filedialog.askopenfilename(parent = self, 
+            title = "Open SAVEx.DAT file",
+            filetypes = ft,
+            initialdir = os.path.abspath(os.curdir))
+        if not fn: return
+        os.chdir(os.path.dirname(osfn))
+        if self.open_savedat_from(fn):
+            self.open_path("/save")
+
+    def open_savedat_from(self, fn):
+        if self.sim is None:
+            self.switch_view(0)
+            self.update_gui("")
+            self.clear_info()
+            self.add_info("Open data before loading saves")  
+            return
+        self.save = None
+        try:
+            self.save = petka.SaveLoader("cp1251")
+            with open(fn, "rb") as f:
+                self.save.load_data(f, self.sim.curr_part, 
+                    len(self.sim.objects) + len(self.sim.scenes))
+                if self.save.part != self.sim.curr_part:
+                    # load
+                    print("DEBUG: change part {}".format(self.save.part))
+                    self.sim.open_part(self.save.part, 0)
+                    f.seek(0)
+                    self.save.load_data(f, self.sim.curr_part, 
+                        len(self.sim.objects) + len(self.sim.scenes))
+            return True
+        except:
+            print("DEBUG: Error opening SAVEx.DAT")
+            self.save = None
+            self.switch_view(0)
+            self.update_gui("")
+            self.clear_info()
+            self.add_info("Error opening \"{}\" \n\n{}".\
+                format(hlesc(fn), hlesc(traceback.format_exc())))
             
 
     def on_tran_save(self):
@@ -2845,6 +2950,9 @@ def main():
         elif argv[0] == "-s": # open str file
             app.start_act.append(["str", argv[1]])
             argv = argv[2:]
+        elif argv[0] == "-sd": # open savex.dat file
+            app.start_act.append(["savedat", argv[1]])
+            argv = argv[2:]
         elif argv[0] == "-t": # open translation
             app.start_act.append(["tran", argv[1]])
             argv = argv[2:]
diff --git a/engines/petka/petka/__init__.py b/engines/petka/petka/__init__.py
index e48578003..775345ee1 100644
--- a/engines/petka/petka/__init__.py
+++ b/engines/petka/petka/__init__.py
@@ -7,4 +7,5 @@ from .imgbmp import BMPLoader
 from .imgflc import FLCLoader
 from .imgleg import LEGLoader
 from .imgmsk import MSKLoader
+from .saves import SaveLoader
 
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 87488c6ac..0f164244f 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -659,3 +659,6 @@ class Engine:
         for op in self.dlgops:
             f.write(struct.pack("<H2B", op.ref, op.arg, op.opcode))
 
+    def load_save(self, ls):
+        pass
+
diff --git a/engines/petka/petka/fman.py b/engines/petka/petka/fman.py
index 427c9a752..e83d68682 100644
--- a/engines/petka/petka/fman.py
+++ b/engines/petka/petka/fman.py
@@ -72,7 +72,7 @@ class FileManager:
         # add file descriptor
         self.strfd.append((f, name, tag, strlst))
         print("DEBUG: Loaded store \"{}\"".format(name))
-        
+
     def read_file(self, fname):
         sf = fname.lower().replace("\\", "/")
         if sf in self.strtable:
diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
index 7f3ec4c4e..26e8261ba 100644
--- a/engines/petka/petka/imgbmp.py
+++ b/engines/petka/petka/imgbmp.py
@@ -69,8 +69,8 @@ class BMPLoader:
                 
         return pictw, picth, picture_data
         
-    def pixelswap16(self, pw, ph, pd):
-        # convert 16 bit to 24
+    def pixelswap16ud(self, pw, ph, pd):
+        # convert 16 bit to 24 + vertical reverse
         b16arr = array.array("H") # unsigned short
         b16arr.frombytes(pd)
         b16arr.byteswap()
@@ -84,6 +84,21 @@ class BMPLoader:
                 rgb[off + 2] = (b16 >> 8) & 0b11111000
         return rgb
 
+    def pixelswap16(self, pw, ph, pd):
+        # convert 16 bit to 24
+        b16arr = array.array("H") # unsigned short
+        b16arr.frombytes(pd)
+        #b16arr.byteswap()
+        rgb = array.array("B", [0] * pw * ph * 3)
+        for j in range(ph):
+            for i in range(pw):
+                off = j * pw * 3 + i * 3
+                b16 = b16arr[j * pw + i]
+                rgb[off + 2] = (b16 << 3) & 0b11111000
+                rgb[off + 1] = (b16 >> 3) & 0b11111100
+                rgb[off + 0] = (b16 >> 8) & 0b11111000
+        return rgb
+
     def load_info(self, f):
         try:
             pw, ph, pd = self.load_data_int16(f)
@@ -93,11 +108,20 @@ class BMPLoader:
             f.seek(0)
             self.image = Image.open(f)
         
+    def load_raw(self, pw, ph, pd):
+        if Image:
+            pd = self.pixelswap16(pw, ph, pd).tobytes()
+            self.image = Image.frombytes("RGB", (pw, ph), pd) 
+        else:
+            self.width = pw
+            self.height = ph
+            self.rgb = self.pixelswap16(pw, ph, pd)
+        
     def load_data(self, f):
         try:
             pw, ph, pd = self.load_data_int16(f)
             if Image:
-                pd = self.pixelswap16(pw, ph, pd).tobytes()
+                pd = self.pixelswap16ud(pw, ph, pd).tobytes()
                 self.image = Image.frombytes("RGB", (pw, ph), pd) 
             else:
                 self.width = pw
@@ -106,4 +130,4 @@ class BMPLoader:
         except:
             f.seek(0)
             self.image = Image.open(f)
-            
+
diff --git a/engines/petka/petka/saves.py b/engines/petka/petka/saves.py
new file mode 100644
index 000000000..f565577bb
--- /dev/null
+++ b/engines/petka/petka/saves.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+
+# romiq.kh at gmail.com, 2014
+
+import array, struct, io
+import binascii
+
+from . import EngineError, BMPLoader
+
+
+class SaveLoader:
+    def __init__(self, enc = None):
+        self.part = None
+        self.chap = None
+        self.stamp = None
+        self.enc = enc
+       
+    def load_data(self, f, part, objnum):
+        data = f.read(8)
+        self.part, self.chap = struct.unpack("<2I", data)
+        if self.part != part: return
+
+        # read date stamp, asciiz
+        stamp = b""
+        while len(stamp) < 30:
+            ch = f.read(1)
+            if ch == b"\x00": break
+            stamp += ch
+        self.stamp = stamp.decode(self.enc)
+        print("Saved at", len(self.stamp), repr(self.stamp))
+        hz1 = f.read(max(0, 29 - len(stamp)))
+        #print("HZ1", hz1)
+        
+        # read screenshot
+        sl = 108 * 81 * 2
+        rgb = f.read(sl)
+        if len(rgb) != sl:
+            raise EngineError("Bad SAVE length (no screenshot)")
+        self.shot = BMPLoader()
+        self.shot.load_raw(108, 81, rgb)
+               
+        print("RGB beg", binascii.hexlify(rgb[:8]))
+        print("RGB end", binascii.hexlify(rgb[-8:]))
+
+        hz2 = f.read(216)
+        if hz2 != b"\x00" * 216:
+            print("HZ2", hz2) # all zeroes?
+            raise EngineError("Bad SAVE error in HZ2 field")
+            
+        #print("HZ2", hz2) # all zeroes?
+        data = f.read(4)
+        hz3 = struct.unpack("<I", data)[0]
+        print("HZ3", hz3)
+        
+        def readstr():
+            data = f.read(4)
+            strlen = struct.unpack("<I", data)[0]
+            s = f.read(strlen)
+            #print("STR", strlen, data, s)
+            return s.decode(self.enc)
+        
+        print("Req", objnum)
+        for i in range(objnum):
+            s1 = readstr()
+            s2 = readstr()
+            data = f.read(33)
+            #print(i, s1, s2)
+            
+        
+        # scene
+        data = f.read(4)
+        hz4len = struct.unpack("<I", data)[0]
+        data = f.read(hz4len * 2)
+        hz4 = struct.unpack("<{}H".format(hz4len), data)
+        print("HZ4", hz4)
+
+        self.scene = readstr()
+        print("Scene:", self.scene)
+        
+        
+        data = f.read()
+        print(len(data))


Commit: 762ebd64edf828e6d1bc30eaed5c9a8647cc90f9
    https://github.com/scummvm/scummvm-tools/commit/762ebd64edf828e6d1bc30eaed5c9a8647cc90f9
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: saved decoding: inventory

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/saves.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 642ddfae0..29a21e6da 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -10,6 +10,7 @@ from idlelib.WidgetRedirector import WidgetRedirector
 import traceback
 import urllib.parse
 import math
+import binascii
 
 # Image processing
 try:
@@ -2419,7 +2420,8 @@ class App(tkinter.Frame):
             fid = None
             self.switch_view(0)
             if self.save is None:
-                self.add_info("<b>Saved state:not loaded</b>\n\n")
+                self.add_info("<b>Saved state</b>: not loaded\n\n")
+                return
                 
             if path == ("save",):
                 path = ("save", "info")
@@ -2436,6 +2438,40 @@ class App(tkinter.Frame):
                     self.save.part, self.save.chap))
                 self.add_info("  stamp: " + hlesc(self.save.stamp) + "\n")
                 self.add_info("  scene: " + hlesc(self.save.scene) + "\n")
+                
+                self.add_info("  invntr: {}\n".format(len(self.save.invntr)))
+                fmt = "    " + fmt_dec(len(self.save.invntr)) + ") "
+                for idx, inv in enumerate(self.save.invntr):
+                        self.add_info(fmt.format(idx + 1) + 
+                            self.fmt_hl_obj_scene(inv, True) + "\n")
+
+                self.add_info("\n  objects: {}\n".format(len(
+                    self.save.objects)))
+                fmt = "  " + fmt_dec(len(self.save.objects)) + ") \"{}\" {}\n"
+
+                for idx, obj in enumerate(self.save.objects):
+                    self.add_info(fmt.format(idx + 1, obj["name"], 
+                        obj["alias"]))
+                    fndobj = None
+                    for sobj in self.sim.objects + self.sim.scenes:
+                        if sobj.name == obj["name"]:
+                            fndobj = sobj
+                            break
+                    if fndobj:
+                        self.add_info("    " + 
+                            self.fmt_hl_obj_scene(fndobj.idx, True) + "\n")
+                    
+                    self.add_info("    {}, {}, {}, {}, {}, {}, {}, {}, {}\n".
+                        format(*obj["recs"]))
+                    if obj["res"] in self.sim.res:
+                        res_id = obj["res"]
+                        self.add_info("    " + fmt_hl("/res/all/{}".
+                            format(res_id), "{}".format(res_id)) + 
+                            " (0x{:X}) - {}\n".format(res_id,
+                            hlesc(self.sim.res[res_id])))
+
+                    
+                
             else:
                 self.select_lb_item("info")
                         
@@ -2573,8 +2609,8 @@ class App(tkinter.Frame):
                 hlesc(self.strfm.root)))
 
         self.add_info("<b>Saved state</b>: ")
-        if not self.strfm:
-            self.add_info("<i>not loaded</i>\n")
+        if not self.save:
+            self.add_info("<i>not loaded</i>\n\n")
         else:
             self.add_info("<i>loaded</i>\n\n")
 
@@ -2733,7 +2769,6 @@ class App(tkinter.Frame):
             self.clear_hist()
 
     def open_str_from(self, fn):
-        self.last_fn = fn
         self.clear_data()
         try:
             self.strfm = petka.FileManager(os.path.dirname(fn))
@@ -2757,7 +2792,7 @@ class App(tkinter.Frame):
             filetypes = ft,
             initialdir = os.path.abspath(os.curdir))
         if not fn: return
-        os.chdir(os.path.dirname(osfn))
+        os.chdir(os.path.dirname(os.path.dirname(fn)))
         if self.open_savedat_from(fn):
             self.open_path("/save")
 
diff --git a/engines/petka/petka/saves.py b/engines/petka/petka/saves.py
index f565577bb..ef70363bb 100644
--- a/engines/petka/petka/saves.py
+++ b/engines/petka/petka/saves.py
@@ -14,6 +14,7 @@ class SaveLoader:
         self.chap = None
         self.stamp = None
         self.enc = enc
+        self.objects = []
        
     def load_data(self, f, part, objnum):
         data = f.read(8)
@@ -21,15 +22,10 @@ class SaveLoader:
         if self.part != part: return
 
         # read date stamp, asciiz
-        stamp = b""
-        while len(stamp) < 30:
-            ch = f.read(1)
-            if ch == b"\x00": break
-            stamp += ch
+        stamp = f.read(30)
+        stamp = stamp.split(b"\x00")[0]
         self.stamp = stamp.decode(self.enc)
         print("Saved at", len(self.stamp), repr(self.stamp))
-        hz1 = f.read(max(0, 29 - len(stamp)))
-        #print("HZ1", hz1)
         
         # read screenshot
         sl = 108 * 81 * 2
@@ -64,16 +60,20 @@ class SaveLoader:
             s1 = readstr()
             s2 = readstr()
             data = f.read(33)
+            obj = {"name": s1, "alias": s2, "data": data}
+            recs = struct.unpack("<B8i", data)
+            obj["recs"] = recs
+            obj["res"] = recs[2]
+            self.objects.append(obj)
             #print(i, s1, s2)
             
-        
-        # scene
+        # invntr        
         data = f.read(4)
-        hz4len = struct.unpack("<I", data)[0]
-        data = f.read(hz4len * 2)
-        hz4 = struct.unpack("<{}H".format(hz4len), data)
-        print("HZ4", hz4)
+        invlen = struct.unpack("<I", data)[0]
+        data = f.read(invlen * 2)
+        self.invntr = struct.unpack("<{}H".format(invlen), data)
 
+        # scene
         self.scene = readstr()
         print("Scene:", self.scene)
         


Commit: a820343b11459ef88fc05e6457bd9963d0543636
    https://github.com/scummvm/scummvm-tools/commit/a820343b11459ef88fc05e6457bd9963d0543636
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: saves decoding: charter positions, cursor resources

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/__init__.py
    engines/petka/petka/engine.py
    engines/petka/petka/saves.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 29a21e6da..497721134 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -908,6 +908,14 @@ class App(tkinter.Frame):
             return fmt
         return "{} (0x{:X})".format(rec_id, rec_id)
         
+    def fmt_hl_res(self, resid, full = True):
+        if resid in self.sim.res:
+            lnk = fmt_hl("/res/all/{}".format(resid), resid)
+            if full:
+                lnk += " (0x{:X}) - {}".format(resid,
+                    hlesc(self.sim.res[resid]))
+            return lnk
+        
     def fmt_hl_obj(self, obj_id, full = False):
         return self.fmt_hl_rec(self.sim.obj_idx, "objs", obj_id, full, "obj")
         
@@ -947,6 +955,12 @@ class App(tkinter.Frame):
     def fmt_hl_dlg(self, grp_id, full = False):
         return self.fmt_hl_rec(self.sim.dlg_idx, "dlgs", grp_id, full, "dlg")
 
+    def fmt_hl_file(self, fn, capt = None):
+        fnl = fn.lower().replace("\\", "/")
+        capt = capt or fn
+        fid = urllib.parse.quote_plus(fnl)
+        return fmt_hl("/files/{}".format(fid), capt)
+        
     def path_info_outline(self):
         if self.sim is None and self.strfm is None:
             self.add_info("No data loaded. Open PARTS.INI or SCRIPT.DAT first.")
@@ -1499,9 +1513,7 @@ class App(tkinter.Frame):
                 self.add_info("\n<b>Used resources</b>: {}\n".\
                     format(len(resused)))
                 for res_id in resused:
-                    self.add_info("  " + fmt_hl("/res/all/{}".format(res_id), 
-                        "{}".format(res_id)) + " (0x{:X}) - {}\n".format(res_id,
-                            hlesc(self.sim.res[res_id])))
+                    self.add_info("  " + self.fmt_hl_res(res_id, True) + "\n")
             if len(dlgused) > 0:
                 self.add_info("\n<b>Used dialog groups</b>: {}\n".\
                     format(len(dlgused)))
@@ -2109,13 +2121,7 @@ class App(tkinter.Frame):
                         self.fmt_hl_obj_scene(obj_idx, True))))
                 self.add_info("\n")  
         return True
-
-    def fmt_hl_file(self, fn, capt = None):
-        fnl = fn.lower().replace("\\", "/")
-        capt = capt or fn
-        fid = urllib.parse.quote_plus(fnl)
-        return "<a href=\"/files/{}\">{}</a>".format(fid, hlesc(capt))                   
-        
+       
     def path_stores(self, path):
         if self.strfm is None:
             return self.path_default([])
@@ -2434,10 +2440,21 @@ class App(tkinter.Frame):
             elif path[1] == "info":
                 self.clear_info()
                 self.add_info("<b>Saved state:</b>\n\n")
-                self.add_info("  part:  {}, chapter {}\n".format(
+                self.add_info("  part:   {}, chapter {}\n".format(
                     self.save.part, self.save.chap))
-                self.add_info("  stamp: " + hlesc(self.save.stamp) + "\n")
-                self.add_info("  scene: " + hlesc(self.save.scene) + "\n")
+                self.add_info("  stamp:  " + hlesc(self.save.stamp) + "\n")
+                self.add_info("  scene:  " + hlesc(self.save.scene) + "\n")
+                self.add_info("  cursor: {} - {}\n".format(self.save.cursor,
+                    petka.ACTIONS.get(self.save.cursor, ["ACT{:2X}".format(
+                        self.save.cursor), 0])[0]))
+                self.add_info("    " + self.fmt_hl_res(self.save.cursor_res, 
+                    True) + "\n")
+                self.add_info("  char 1: {}, {} ".format(self.save.char1[0], 
+                    self.save.char1[1]) + self.fmt_hl_res(
+                    self.save.char1[2], True) + "\n")
+                self.add_info("  char 2: {}, {} ".format(self.save.char2[0], 
+                    self.save.char2[1]) + self.fmt_hl_res(
+                    self.save.char2[2], True) + "\n")
                 
                 self.add_info("  invntr: {}\n".format(len(self.save.invntr)))
                 fmt = "    " + fmt_dec(len(self.save.invntr)) + ") "
@@ -2460,17 +2477,11 @@ class App(tkinter.Frame):
                     if fndobj:
                         self.add_info("    " + 
                             self.fmt_hl_obj_scene(fndobj.idx, True) + "\n")
-                    
                     self.add_info("    {}, {}, {}, {}, {}, {}, {}, {}, {}\n".
                         format(*obj["recs"]))
                     if obj["res"] in self.sim.res:
-                        res_id = obj["res"]
-                        self.add_info("    " + fmt_hl("/res/all/{}".
-                            format(res_id), "{}".format(res_id)) + 
-                            " (0x{:X}) - {}\n".format(res_id,
-                            hlesc(self.sim.res[res_id])))
-
-                    
+                        self.add_info("    " + self.fmt_hl_res(obj["res"], 
+                            True) + "\n")
                 
             else:
                 self.select_lb_item("info")
diff --git a/engines/petka/petka/__init__.py b/engines/petka/petka/__init__.py
index 775345ee1..7c2864ce2 100644
--- a/engines/petka/petka/__init__.py
+++ b/engines/petka/petka/__init__.py
@@ -1,7 +1,7 @@
 
 class EngineError(Exception): pass
 
-from .engine import Engine, OPCODES, DLGOPS
+from .engine import Engine, OPCODES, DLGOPS, ACTIONS
 from .fman import FileManager
 from .imgbmp import BMPLoader
 from .imgflc import FLCLoader
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 0f164244f..58c19d36e 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -80,6 +80,14 @@ DLGOPS = {
     8:  ("CIRCLE",      6),
 }
 
+ACTIONS = {
+    0: ("VIEW",    5002),
+    1: ("GOTO",    5003),
+    2: ("USE",     5004),
+    3: ("TAKE",    5005),
+    4: ("TALK",    5006),
+}
+
 class ScrObject:
     def __init__(self, idx, name):
         self.idx = idx
diff --git a/engines/petka/petka/saves.py b/engines/petka/petka/saves.py
index ef70363bb..e9581db44 100644
--- a/engines/petka/petka/saves.py
+++ b/engines/petka/petka/saves.py
@@ -77,6 +77,21 @@ class SaveLoader:
         self.scene = readstr()
         print("Scene:", self.scene)
         
+        # char positions
+        
         
         data = f.read()
         print(len(data))
+        
+        items = struct.unpack("<{}I".format(len(data) // 4), data)
+        print(items[:20])
+        
+        # cursor and res-num (-13 and -14)
+        self.cursor = items[-12]
+        self.cursor_res = items[-13]
+        
+        # charters: x, y, res
+        self.char1 = (items[0], items[1], items[-10])
+        self.char2 = (items[2], items[3], items[-9])
+        
+


Commit: d8b4720443567ac3926a0d415384c32613e95af4
    https://github.com/scummvm/scummvm-tools/commit/d8b4720443567ac3926a0d415384c32613e95af4
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: saves: struct decoded

Changed paths:
    engines/petka/petka/saves.py


diff --git a/engines/petka/petka/saves.py b/engines/petka/petka/saves.py
index e9581db44..8de59a583 100644
--- a/engines/petka/petka/saves.py
+++ b/engines/petka/petka/saves.py
@@ -25,7 +25,6 @@ class SaveLoader:
         stamp = f.read(30)
         stamp = stamp.split(b"\x00")[0]
         self.stamp = stamp.decode(self.enc)
-        print("Saved at", len(self.stamp), repr(self.stamp))
         
         # read screenshot
         sl = 108 * 81 * 2
@@ -35,18 +34,16 @@ class SaveLoader:
         self.shot = BMPLoader()
         self.shot.load_raw(108, 81, rgb)
                
-        print("RGB beg", binascii.hexlify(rgb[:8]))
-        print("RGB end", binascii.hexlify(rgb[-8:]))
-
         hz2 = f.read(216)
         if hz2 != b"\x00" * 216:
             print("HZ2", hz2) # all zeroes?
             raise EngineError("Bad SAVE error in HZ2 field")
             
-        #print("HZ2", hz2) # all zeroes?
         data = f.read(4)
         hz3 = struct.unpack("<I", data)[0]
-        print("HZ3", hz3)
+
+        if hz3 != objnum + 3:
+            raise EngineError("Bad SAVE objects number")
         
         def readstr():
             data = f.read(4)
@@ -55,7 +52,6 @@ class SaveLoader:
             #print("STR", strlen, data, s)
             return s.decode(self.enc)
         
-        print("Req", objnum)
         for i in range(objnum):
             s1 = readstr()
             s2 = readstr()
@@ -78,20 +74,22 @@ class SaveLoader:
         print("Scene:", self.scene)
         
         # char positions
+        data = f.read(16)
+        charpos = struct.unpack("<4I", data)
         
+        # arr hz5
+        data = f.read(4)
+        hz5len = struct.unpack("<I", data)[0]
+        hz5 = struct.unpack("<{}I".format(hz5len), f.read(hz5len * 4))
         
-        data = f.read()
-        print(len(data))
-        
-        items = struct.unpack("<{}I".format(len(data) // 4), data)
-        print(items[:20])
-        
-        # cursor and res-num (-13 and -14)
-        self.cursor = items[-12]
-        self.cursor_res = items[-13]
+        data = f.read(52)
+        self.cursor_res, self.cursor, hz6, c1res, c2res, *hz = \
+            struct.unpack("<13I", data)
         
         # charters: x, y, res
-        self.char1 = (items[0], items[1], items[-10])
-        self.char2 = (items[2], items[3], items[-9])
-        
+        self.char1 = (charpos[0], charpos[1], c1res)
+        self.char2 = (charpos[2], charpos[3], c2res)
+
+        if f.read():
+            raise EngineError("Bad SAVE length (extra data)")
 


Commit: 34ad33b6b16ff6ce30b7c190cb4db966c8216756
    https://github.com/scummvm/scummvm-tools/commit/34ad33b6b16ff6ce30b7c190cb4db966c8216756
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: saves display

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/saves.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 497721134..6ac863ef3 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -188,7 +188,6 @@ class App(tkinter.Frame):
         self.curr_path = []
         self.curr_help = ""
         self.last_path = [None]
-        self.last_fn = ""
         self.curr_gui = []
         self.curr_state = {} # local state for location group
         self.curr_lb_acts = None
@@ -205,10 +204,12 @@ class App(tkinter.Frame):
         
     def clear_data(self):
         self.sim = None
+        self.last_fn = ""
         # store manager
         self.strfm = None
         # save
         self.save = None
+        self.last_savefn = ""
         # translation
         self.tran = None
            
@@ -2146,7 +2147,8 @@ class App(tkinter.Frame):
             self.upd_toolgrp(self.curr_state["btnsort"], sm)
             self.curr_state["btnext"].config(state = tkinter.DISABLED)
             if stid is None:
-                self.add_info("<b>Stores</b>\n\n")
+                self.add_info("<b>Store manager:</b> {}\n\n".format(
+                    self.strfm.root))
                 fmt = "  " + fmt_dec(len(self.strfm.strfd)) + \
                     ") <a href=\"/strs/{}\">{}</a> (tag={})\n"
                 for idx, st in enumerate(self.strfm.strfd):
@@ -2439,7 +2441,8 @@ class App(tkinter.Frame):
                 self.update_canvas()
             elif path[1] == "info":
                 self.clear_info()
-                self.add_info("<b>Saved state:</b>\n\n")
+                self.add_info("<b>Saved state:</b> {}\n\n".format(
+                    self.last_savefn))
                 self.add_info("  part:   {}, chapter {}\n".format(
                     self.save.part, self.save.chap))
                 self.add_info("  stamp:  " + hlesc(self.save.stamp) + "\n")
@@ -2482,6 +2485,18 @@ class App(tkinter.Frame):
                     if obj["res"] in self.sim.res:
                         self.add_info("    " + self.fmt_hl_res(obj["res"], 
                             True) + "\n")
+            elif path[1] in ["arr5", "arr6"]:
+                self.clear_info()
+                if path[1] == "arr5":
+                    arr = self.save.hz5
+                else:
+                    arr = self.save.hz
+                self.add_info("<b>Array {}:</b> len={}\n\n".format(
+                    path[1][3:], len(arr)))
+                fmt = "  " + fmt_dec(len(arr)) + ") {}\n"
+                for idx, val in enumerate(arr):
+                    self.add_info(fmt.format(idx + 1, hex(val)))
+                    
                 
             else:
                 self.select_lb_item("info")
@@ -2491,6 +2506,8 @@ class App(tkinter.Frame):
             self.update_gui("Save")
             self.insert_lb_act("Info", ["save"], "info")
             self.insert_lb_act("Screenshot", ["save", "shot"], "shot")
+            self.insert_lb_act("Array 5", ["save", "arr5"], "arr5")
+            self.insert_lb_act("Array 6", ["save", "arr6"], "arr6")
         upd_save()
         return True
             
@@ -2615,15 +2632,15 @@ class App(tkinter.Frame):
         if not self.strfm:
             self.add_info("<i>not initialized</i>\n")
         else:
-            self.add_info("<i>works</i>\n\n")
-            self.add_info("  <b>Path</b>: {}\n\n".format(
-                hlesc(self.strfm.root)))
+            self.add_info("<i>works</i>\n")
+            self.add_info("  path: {}\n\n".format(hlesc(self.strfm.root)))
 
         self.add_info("<b>Saved state</b>: ")
         if not self.save:
             self.add_info("<i>not loaded</i>\n\n")
         else:
-            self.add_info("<i>loaded</i>\n\n")
+            self.add_info("<i>loaded</i>\n")
+            self.add_info("  path: {}\n\n".format(hlesc(self.last_savefn)))
 
         if self.sim or self.strfm:
             self.path_info_outline()
@@ -2816,6 +2833,7 @@ class App(tkinter.Frame):
             return
         self.save = None
         try:
+            self.last_savefn = fn
             self.save = petka.SaveLoader("cp1251")
             with open(fn, "rb") as f:
                 self.save.load_data(f, self.sim.curr_part, 
diff --git a/engines/petka/petka/saves.py b/engines/petka/petka/saves.py
index 8de59a583..ebb5d547a 100644
--- a/engines/petka/petka/saves.py
+++ b/engines/petka/petka/saves.py
@@ -71,7 +71,6 @@ class SaveLoader:
 
         # scene
         self.scene = readstr()
-        print("Scene:", self.scene)
         
         # char positions
         data = f.read(16)
@@ -80,10 +79,10 @@ class SaveLoader:
         # arr hz5
         data = f.read(4)
         hz5len = struct.unpack("<I", data)[0]
-        hz5 = struct.unpack("<{}I".format(hz5len), f.read(hz5len * 4))
+        self.hz5 = struct.unpack("<{}I".format(hz5len), f.read(hz5len * 4))
         
         data = f.read(52)
-        self.cursor_res, self.cursor, hz6, c1res, c2res, *hz = \
+        self.cursor_res, self.cursor, hz6, c1res, c2res, *self.hz = \
             struct.unpack("<13I", data)
         
         # charters: x, y, res
@@ -93,3 +92,5 @@ class SaveLoader:
         if f.read():
             raise EngineError("Bad SAVE length (extra data)")
 
+        print("HZ5", hz5len, hz5len / objnum, hz5len / hz3)
+


Commit: b266ee00dd795ddc518631f15fce0ede0ede26de
    https://github.com/scummvm/scummvm-tools/commit/b266ee00dd795ddc518631f15fce0ede0ede26de
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fixes

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index e960e2f31..db02fb92c 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,6 +1,6 @@
 Что нового
 ==========
-2014-12-27 версия 0.3g
+2014-12-29 версия 0.3g
 ----------------------
 Исправлено отображение в списке фраз (сообщений)
 Добавлены справочные файлы о опкодах (объектов и  диалогов)
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 6ac863ef3..0df00e928 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -31,7 +31,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.3g 2014-12-27"
+VERSION = "v0.3g 2014-12-29"
 
 def hlesc(value):
     if value is None:


Commit: f5a459b9aea1d4b7bc138df619762a4a18bddff9
    https://github.com/scummvm/scummvm-tools/commit/f5a459b9aea1d4b7bc138df619762a4a18bddff9
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fix dislog opcodes info

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index db02fb92c..835da3a1c 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,5 +1,9 @@
 Что нового
 ==========
+2015-01-12 версия 0.3h
+----------------------
+Исправлено отображение списка диалоговых групп где используется указанный опкод
+  (ошибка появилась в 0.3g)
 2014-12-29 версия 0.3g
 ----------------------
 Исправлено отображение в списке фраз (сообщений)
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 0df00e928..046c9d869 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -31,7 +31,7 @@ except ImportError:
 import petka
 
 APPNAME = "P1&2 Explorer"
-VERSION = "v0.3g 2014-12-29"
+VERSION = "v0.3h 2015-01-12"
 
 def hlesc(value):
     if value is None:
@@ -2112,7 +2112,7 @@ class App(tkinter.Frame):
                 self.add_info("<i>Not used in dialogs</i>\n\n")
             else:            
                 self.add_info("<i>Used in dialogs</i>: {}\n".format(len(dls)))
-                fmtdls = "  " + fmt_dec(len(dacts)) + \
+                fmtdls = "  " + fmt_dec(len(dls)) + \
                     ") obj={}, group=<a href=\"/dlgs/{}\">{}" + \
                         "</a>, act={}, dlg={}, op={} {}\n"
                 for idx, (obj_idx, gidx, aidx, didx, oidx) in enumerate(dls):


Commit: 7f09c7ed2a4e31dedcf6566417a38f63649eb03c
    https://github.com/scummvm/scummvm-tools/commit/7f09c7ed2a4e31dedcf6566417a38f63649eb03c
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: saves almost decoded

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py
    engines/petka/petka/engine.py
    engines/petka/petka/saves.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 835da3a1c..f679ca225 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -4,6 +4,8 @@
 ----------------------
 Исправлено отображение списка диалоговых групп где используется указанный опкод
   (ошибка появилась в 0.3g)
+Загрузка сохранений доработана
+
 2014-12-29 версия 0.3g
 ----------------------
 Исправлено отображение в списке фраз (сообщений)
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 046c9d869..170284924 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -2452,6 +2452,11 @@ class App(tkinter.Frame):
                         self.save.cursor), 0])[0]))
                 self.add_info("    " + self.fmt_hl_res(self.save.cursor_res, 
                     True) + "\n")
+                if self.save.cursor_obj == 0xffffffff:
+                    self.add_info("  object: <i>none</i>\n")
+                else:
+                    self.add_info("  object: " + self.fmt_hl_obj(
+                        self.save.cursor_obj, True) + "\n")
                 self.add_info("  char 1: {}, {} ".format(self.save.char1[0], 
                     self.save.char1[1]) + self.fmt_hl_res(
                     self.save.char1[2], True) + "\n")
@@ -2485,19 +2490,14 @@ class App(tkinter.Frame):
                     if obj["res"] in self.sim.res:
                         self.add_info("    " + self.fmt_hl_res(obj["res"], 
                             True) + "\n")
-            elif path[1] in ["arr5", "arr6"]:
+            elif path[1] == "dlgops":
                 self.clear_info()
-                if path[1] == "arr5":
-                    arr = self.save.hz5
-                else:
-                    arr = self.save.hz
-                self.add_info("<b>Array {}:</b> len={}\n\n".format(
-                    path[1][3:], len(arr)))
-                fmt = "  " + fmt_dec(len(arr)) + ") {}\n"
-                for idx, val in enumerate(arr):
-                    self.add_info(fmt.format(idx + 1, hex(val)))
-                    
-                
+                self.add_info("<b>Dialog opcodes:</b> len = {} (0x{:x})\n\n".
+                    format(len(self.save.dlgops), len(self.save.dlgops)))
+                fmt = "  " + fmt_dec(len(self.save.dlgops)) + ") {} {:02x} " +\
+                    "{:04x}\n"
+                for idx, (code, arg, ref) in enumerate(self.save.dlgops):
+                    self.add_info(fmt.format(idx + 1, self.fmt_dlgop(code), arg, ref))
             else:
                 self.select_lb_item("info")
                         
@@ -2506,8 +2506,7 @@ class App(tkinter.Frame):
             self.update_gui("Save")
             self.insert_lb_act("Info", ["save"], "info")
             self.insert_lb_act("Screenshot", ["save", "shot"], "shot")
-            self.insert_lb_act("Array 5", ["save", "arr5"], "arr5")
-            self.insert_lb_act("Array 6", ["save", "arr6"], "arr6")
+            self.insert_lb_act("Dialog opcodes", ["save", "dlgops"], "dlgops")
         upd_save()
         return True
             
@@ -2716,6 +2715,7 @@ class App(tkinter.Frame):
             self.update_gui("Info")
             self.insert_lb_act("Opcodes", ["info", "opcodes"], "opcodes")
             self.insert_lb_act("Dialog opcodes", ["info", "dlgops"], "dlgops")
+            self.insert_lb_act("Actions", ["info", "acts"], "acts")
         # change
         name = None
         if len(path) > 1:
@@ -2744,6 +2744,15 @@ class App(tkinter.Frame):
                 for key in k:
                     self.add_info("  {} (0x{:X}) - {}\n".format(key, key,
                         petka.DLGOPS[key][0]))
+            elif name == "acts":
+                self.add_info("<b>Actions<b>\n\n")
+                k = list(petka.ACTIONS.keys())
+                k.sort()
+                for key in k:
+                    self.add_info("  {} (0x{:X}) - {}\n".format(key, key,
+                        petka.ACTIONS[key][0]))
+                    self.add_info("    " + 
+                        self.fmt_hl_res(petka.ACTIONS[key][1]) + "\n")
             else: 
                 self.add_info("Unknown data type \"{}\"\n".format(hlesc(name)))
 
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 58c19d36e..30c437bea 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -86,6 +86,9 @@ ACTIONS = {
     2: ("USE",     5004),
     3: ("TAKE",    5005),
     4: ("TALK",    5006),
+    5: ("CHAPAY",  5007),
+    6: ("INVNTR",    -1),
+    
 }
 
 class ScrObject:
@@ -167,8 +170,8 @@ class Engine:
         self.enc = enc
         self.objects = []
         self.scenes = []
-        self.obj_idx = {}
-        self.scn_idx = {}
+        self.obj_idx = {} # id -> object
+        self.scn_idx = {} # id -> scene
         self.msgs = []
         self.dlgs = []
         self.dlg_idx = {}
@@ -590,7 +593,7 @@ class Engine:
                 num_ops = struct.unpack_from("<I", temp)[0]
                 for oidx, i in enumerate(range(num_ops)):
                     temp = f.read(4)
-                    ref, arg, code  = struct.unpack_from("<HBB", temp)
+                    ref, arg, code = struct.unpack_from("<HBB", temp)
                     dlgop = DlgOpObject(code, arg, ref)
                     dlgop.pos = oidx
                     if ref < len(self.msgs):
diff --git a/engines/petka/petka/saves.py b/engines/petka/petka/saves.py
index ebb5d547a..ec24a2e8f 100644
--- a/engines/petka/petka/saves.py
+++ b/engines/petka/petka/saves.py
@@ -36,7 +36,6 @@ class SaveLoader:
                
         hz2 = f.read(216)
         if hz2 != b"\x00" * 216:
-            print("HZ2", hz2) # all zeroes?
             raise EngineError("Bad SAVE error in HZ2 field")
             
         data = f.read(4)
@@ -76,21 +75,28 @@ class SaveLoader:
         data = f.read(16)
         charpos = struct.unpack("<4I", data)
         
-        # arr hz5
+        # arr dialog opcodes
         data = f.read(4)
-        hz5len = struct.unpack("<I", data)[0]
-        self.hz5 = struct.unpack("<{}I".format(hz5len), f.read(hz5len * 4))
-        
-        data = f.read(52)
-        self.cursor_res, self.cursor, hz6, c1res, c2res, *self.hz = \
-            struct.unpack("<13I", data)
+        dlgoplen = struct.unpack("<I", data)[0]
+        self.dlgops = []
+        for _ in range(dlgoplen):
+            data = f.read(4)
+            ref, arg, code = struct.unpack_from("<HBB", data)
+            self.dlgops.append([code, arg, ref])
         
+        data = f.read(20)
+        self.cursor_res, self.cursor, self.cursor_obj, c1res, c2res = \
+            struct.unpack("<5I", data)
+
         # charters: x, y, res
         self.char1 = (charpos[0], charpos[1], c1res)
         self.char2 = (charpos[2], charpos[3], c2res)
 
+        hz7 = f.read(32)
+        if hz7 != b"\xff" * 32:
+            raise EngineError("Bad SAVE error in HZ7 field")
+
         if f.read():
             raise EngineError("Bad SAVE length (extra data)")
 
-        print("HZ5", hz5len, hz5len / objnum, hz5len / hz3)
 


Commit: 21155abca77555f34252da9913bfb7504cd9c63f
    https://github.com/scummvm/scummvm-tools/commit/21155abca77555f34252da9913bfb7504cd9c63f
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: saves loading in engine (wip)

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 170284924..3185596cb 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -10,7 +10,6 @@ from idlelib.WidgetRedirector import WidgetRedirector
 import traceback
 import urllib.parse
 import math
-import binascii
 
 # Image processing
 try:
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 30c437bea..82fbebcb5 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -113,6 +113,12 @@ class ScrOpObject:
         self.op_arg2 = op_arg2
         self.op_arg3 = op_arg3
 
+class ScrObjectState:
+    def __init__(self, obj):
+        self.obj = obj
+        self.state = 0
+        self.prop = [0] * 8
+
 class MsgObject:
     def __init__(self, idx, wav, arg1, arg2, arg3):
         self.idx = idx
@@ -150,6 +156,7 @@ class DlgOpObject:
         self.arg = arg          # argument (ref, offset etc.)
         self.ref = ref          # message idx
         self.msg = None         # message
+        self.pos = None
         
 class Engine:
     def __init__(self):
@@ -286,6 +293,15 @@ class Engine:
         # load dialogs
         self.load_dialogs()
         
+        # current state
+        self.curr_scene = None
+        self.curr_obj = None
+        self.curr_dlg = None
+        self.curr_cursor = None
+        self.curr_char1 = None
+        self.curr_char2 = None
+        self.curr_invntr = None
+        
     def load_script(self, scrname = None, bkgname = None, resname = None):
         self.objects = []
         self.scenes = []
@@ -534,10 +550,10 @@ class Engine:
                 if f:
                     f.close()
 
+        # DIALOGUES.FIX
         self.dlgs = []
         self.dlg_idx = {}
         self.dlgops = []
-        # DIALOGUES.FIX
         if fixname is None:
             try:    
                 f = self.fman.read_file_stream(self.curr_path + "dialogue.fix")
@@ -670,6 +686,37 @@ class Engine:
         for op in self.dlgops:
             f.write(struct.pack("<H2B", op.ref, op.arg, op.opcode))
 
-    def load_save(self, ls):
+    def scene_to_id(self, name):
         pass
 
+    # create current state from initial state
+    def init_game(self):
+        self.curr_scene = self.scene_to_id(self.start_scene)
+        
+        self.curr_obj = []
+        for obj in self.objects + self.scenes:
+            state = ScrObjectState(obj)
+            self.curr_obj.append(state)
+            state.prop
+            
+        self.curr_dlg = []
+        for dlgop in self.dlgops:
+            dlgstate = DlgOpObject(dlgop.code, dlgop.arg, dlgop.ref)
+            dlgstate.pos = dlgop.pos
+            self.curr_dlg.append(dlgstate)
+        
+        self.curr_cursor = None
+        self.curr_char1 = None
+        self.curr_char2 = None
+        self.curr_invntr = []        
+
+    def load_save(self, ls):
+        self.curr_scene = self.scene_to_id(ls.scene)
+
+        self.curr_obj = None
+        self.curr_dlg = None
+        self.curr_cursor = None
+        self.curr_char1 = None
+        self.curr_char2 = None
+        self.curr_invntr = None
+


Commit: 31cf5f81264876cf19fc358d8aea795b35a26931
    https://github.com/scummvm/scummvm-tools/commit/31cf5f81264876cf19fc358d8aea795b35a26931
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fix compiler: creating scenes with zero references to objects

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12script.py
    engines/petka/petka/engine.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index f679ca225..c7f0fbd9e 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -5,6 +5,7 @@
 Исправлено отображение списка диалоговых групп где используется указанный опкод
   (ошибка появилась в 0.3g)
 Загрузка сохранений доработана
+Исправлено в компиляторе создание сцен с нулевым количеством ссылок на объекты
 
 2014-12-29 версия 0.3g
 ----------------------
diff --git a/engines/petka/p12script.py b/engines/petka/p12script.py
index 6ccdce86e..107e272c7 100755
--- a/engines/petka/p12script.py
+++ b/engines/petka/p12script.py
@@ -13,7 +13,7 @@ import petka
 import petka.engine
 
 APPNAME = "P1&2 Compiler and decompiler"
-VERSION = "v0.3 2014-06-01"
+VERSION = "v0.3h 2015-01-12"
 
 class ScriptSyntaxError(Exception): pass
 
@@ -462,6 +462,7 @@ class P12Compiler:
         num_bkg = 0
         for citem in compscene:
             scenerec = makerec(citem)
+            scenerec.refs = None
             pe.scenes.append(scenerec)
 
             if citem["ref"] is not None:
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index 82fbebcb5..afe725c62 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -355,6 +355,7 @@ class Engine:
 
         for i in range(num_scn):
             off, scn = read_rec(off)
+            scn.refs = None
             self.scenes.append(scn)
             self.scn_idx[scn.idx] = scn
             


Commit: 9f0f8c56ea550dfeca37ec234d467b85f9eafd7f
    https://github.com/scummvm/scummvm-tools/commit/9f0f8c56ea550dfeca37ec234d467b85f9eafd7f
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: change save decoding

Changed paths:
    engines/petka/p12explore.py
    engines/petka/petka/saves.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 3185596cb..f5f1828d4 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -2314,7 +2314,7 @@ class App(tkinter.Frame):
                                         self.fmt_hl_msg(idx, True)))
 
                 grp = [
-                    [".leg", ".off", ".msk", ".flc"],
+                    [".leg", ".off", ".msk", ".flc", ".ms2"],
                     [".bmp", ".cvx"]
                 ]
                 sg = None
diff --git a/engines/petka/petka/saves.py b/engines/petka/petka/saves.py
index ec24a2e8f..2f5ebda0c 100644
--- a/engines/petka/petka/saves.py
+++ b/engines/petka/petka/saves.py
@@ -56,7 +56,7 @@ class SaveLoader:
             s2 = readstr()
             data = f.read(33)
             obj = {"name": s1, "alias": s2, "data": data}
-            recs = struct.unpack("<B8i", data)
+            recs = struct.unpack("<iB7i", data)
             obj["recs"] = recs
             obj["res"] = recs[2]
             self.objects.append(obj)


Commit: 3219f4b1158a1b70560e6a5af0b806116c94500a
    https://github.com/scummvm/scummvm-tools/commit/3219f4b1158a1b70560e6a5af0b806116c94500a
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: add interlinks for objetcs and dialog groups. allow open home sites

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index c7f0fbd9e..86f3b8009 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -6,6 +6,9 @@
   (ошибка появилась в 0.3g)
 Загрузка сохранений доработана
 Исправлено в компиляторе создание сцен с нулевым количеством ссылок на объекты
+Добавлены переходы из объектов к группамм диалогов с таким же номером, так как
+  они связаны
+Название изменено на "Petka Explorer", добавлены ссылка на сайт
 
 2014-12-29 версия 0.3g
 ----------------------
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index f5f1828d4..ff5ed7589 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -10,6 +10,7 @@ from idlelib.WidgetRedirector import WidgetRedirector
 import traceback
 import urllib.parse
 import math
+import webbrowser
 
 # Image processing
 try:
@@ -29,8 +30,10 @@ except ImportError:
 
 import petka
 
-APPNAME = "P1&2 Explorer"
+APPNAME = "Petka Explorer"
 VERSION = "v0.3h 2015-01-12"
+HOME_URLS = ["http://petka-vich.com/petkaexplorer/", 
+            "https://bitbucket.org/romiq/p12simtran"]
 
 def hlesc(value):
     if value is None:
@@ -482,6 +485,13 @@ class App(tkinter.Frame):
             path = path[:-1]
         return path    
  
+    def open_http(self, path):
+        if path not in HOME_URLS:
+            if not messagebox.askokcancel(parent = self, title = "Visit URL?", 
+                message = "Would you like to open external URL:\n" + path + 
+                " ?"): return
+        webbrowser.open(path)
+ 
     def open_path(self, loc, withhist = True):
         path = self.parse_path(loc)        
         if withhist:
@@ -738,6 +748,8 @@ class App(tkinter.Frame):
                             ref = ref[:-1]
                         def make_cb(path):
                             def cb():
+                                if path[:5] == "http:" or path[:6] == "https:":
+                                    return self.open_http(path)
                                 return self.open_path(path)
                             return cb
                         tags.append(self.text_hl.add(make_cb(ref)))
@@ -1466,6 +1478,9 @@ class App(tkinter.Frame):
 
             resused = []
             dlgused = []
+            # check group with same id
+            if rec.idx in self.sim.dlg_idx:
+                dlgused.append(rec.idx)
             self.add_info("\n<b>Handlers</b>: {}\n".format(len(rec.acts)))
             fmtra = "  " + fmt_dec(len(rec.acts)) + \
                 ") <u>on {}</u>, ops: {}{}\n"
@@ -1531,7 +1546,7 @@ class App(tkinter.Frame):
                     self.add_info("  " + self.fmt_hl_msg(msg.idx, True) + "\n")
 
             oplst = {}
-            # objects tan use this objects in TALK opcode
+            # objects can use this objects in TALK opcode
             wasmsg = False
             for obj2 in self.sim.objects + self.sim.scenes:
                 if obj2.idx == rec.idx: continue
@@ -1887,24 +1902,34 @@ class App(tkinter.Frame):
                         self.add_info("        {}{}{}{}\n".\
                             format(opcode, oparg, opref, cmt))
 
-            def usedby(lst):
-                for idx, rec in enumerate(lst):
+            def usedby(lst, hl):
+                hdr = False
+                for rec in lst:
+                    if grp.idx == rec.idx:
+                        # linked object woth same id
+                        if not hdr:
+                            self.add_info(hl)
+                            hdr = True
+                        self.add_info("  linked " + 
+                            self.fmt_hl_obj_scene(rec.idx, True) + "\n")
+                        continue
                     ru = False
                     for act in rec.acts:
                         if ru: break
                         for op in act.ops:
                             if op.op_code == 0x11 and \
                                     op.op_ref == grp.idx: # DIALOG 
+                                if not hdr:
+                                    self.add_info(hl)
+                                    hdr = True
                                 self.add_info("  " + 
                                     self.fmt_hl_obj_scene(rec.idx, True) + "\n")
                                 ru = True
                                 break
                             #print(op_id, op_code, op_res, op4, op5)
 
-            self.add_info("\n\n<b>Used by objects</b>:\n")
-            usedby(self.sim.objects)
-            self.add_info("\n<b>Used by scenes</b>:\n")
-            usedby(self.sim.scenes)
+            usedby(self.sim.objects, "\n<b>Used by objects</b>:\n")
+            usedby(self.sim.scenes, "\n<b>Used by scenes</b>:\n")
         return True
         
     def path_opcodes(self, path):
@@ -2584,10 +2609,12 @@ class App(tkinter.Frame):
         self.update_gui("About")
         self.insert_lb_act("Outline", [])
         self.clear_info()
-        self.add_info("Welcome to <b>Petka 1 & 2 resource explorer</b>\n\n")
-        self.add_info("  " + APPNAME + " " + VERSION + "\n")
-        self.add_info("  romiq.kh at gmail.com\n")
-        self.add_info("  https://bitbucket.org/romiq/p12simtran\n")
+        self.add_info("Welcome to <b>Petka Explorer</b>\n")
+        self.add_info("  A resource explorer for Petka 1 and 2\n")
+        self.add_info("  " + APPNAME + " " + VERSION + ", ")
+        self.add_info("romiq.kh at gmail.com\n")
+        for u in HOME_URLS:
+          self.add_info("  " + fmt_hl(u, u) + "\n")
         self.add_info("\n")
         self.path_info_outline()
         return True


Commit: 461afeebada4771267778a304528c4d03980a06c
    https://github.com/scummvm/scummvm-tools/commit/461afeebada4771267778a304528c4d03980a06c
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: fix scenes with no referenced objects. add interlinks between objects and scenes

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 86f3b8009..66b240d52 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,5 +1,10 @@
 Что нового
 ==========
+2015-01-19 версия 0.3i
+----------------------
+Исправлено отображение сцен при отсутствии ссылок на объекты
+Добавлена  информация о связаных диалогах у объектов и наоборот
+
 2015-01-12 версия 0.3h
 ----------------------
 Исправлено отображение списка диалоговых групп где используется указанный опкод
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index ff5ed7589..1b89b9ff8 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -31,7 +31,7 @@ except ImportError:
 import petka
 
 APPNAME = "Petka Explorer"
-VERSION = "v0.3h 2015-01-12"
+VERSION = "v0.3i 2015-01-19"
 HOME_URLS = ["http://petka-vich.com/petkaexplorer/", 
             "https://bitbucket.org/romiq/p12simtran"]
 
@@ -1408,7 +1408,11 @@ class App(tkinter.Frame):
         else:
             # record info
             self.add_info(("<b>Object</b>" if isobj \
-                else "<b>Scene</b>") + ":\n")
+                else "<b>Scene</b>") + ":")
+            # check linked dialog
+            if rec.idx in self.sim.dlg_idx:
+                self.add_info(" (dialog " + self.fmt_hl_dlg(rec.idx) + ")")
+            self.add_info("\n")
             self.add_info("  Index:     {} (0x{:X})\n".format(rec.idx, rec.idx))
             self.add_info("  Name:      {}\n".format(hlesc(rec.name)))
             if self.tran:
@@ -1455,26 +1459,29 @@ class App(tkinter.Frame):
                                 self.fmt_hl_scene(scn.idx, True) + "\n")
                             break
             else:
-                if len(rec.refs) == 0:
+                if rec.refs is None:
                     self.add_info("\nNo references\n")
-                else:
-                    self.add_info("\n<b>References</b>: {}\n".\
-                        format(len(rec.refs)))
-                fmtd = "  " + fmt_dec(len(rec.refs)) + ") "
-                for idx, ref in enumerate(rec.refs):
-                    self.add_info(fmtd.format(idx) + 
-                        self.fmt_hl_obj(ref[0].idx))
-                    msg = ""
-                    for arg in ref[1:]:
-                        msg += " "
-                        if arg < 10:
-                            msg += "{}".format(arg)
-                        elif arg == 0xffffffff:
-                            msg += "-1"
-                        else:
-                            msg += "0x{:X}".format(arg)
-                    self.add_info(msg + self.fmt_cmt(" // " + self.fmt_hl_obj(
-                        ref[0].idx, True)) + "\n")
+                else: 
+                    if len(rec.refs) == 0:
+                        self.add_info("\nEmpty references\n")
+                    else:
+                        self.add_info("\n<b>References</b>: {}\n".\
+                            format(len(rec.refs)))
+                    fmtd = "  " + fmt_dec(len(rec.refs)) + ") "
+                    for idx, ref in enumerate(rec.refs):
+                        self.add_info(fmtd.format(idx) + 
+                            self.fmt_hl_obj(ref[0].idx))
+                        msg = ""
+                        for arg in ref[1:]:
+                            msg += " "
+                            if arg < 10:
+                                msg += "{}".format(arg)
+                            elif arg == 0xffffffff:
+                                msg += "-1"
+                            else:
+                                msg += "0x{:X}".format(arg)
+                        self.add_info(msg + self.fmt_cmt(" // " + 
+                            self.fmt_hl_obj(ref[0].idx, True)) + "\n")
 
             resused = []
             dlgused = []
@@ -1802,8 +1809,11 @@ class App(tkinter.Frame):
             self.add_info("Select <b>dialog group</b> from list\n")
         else:
             # grp info
-            self.add_info("<b>Dialog group</b>: {} (0x{:X})\n".format(\
+            self.add_info("<b>Dialog group</b>: {} (0x{:X})".format(\
                 grp.idx, grp.idx))
+            if grp.idx in self.sim.obj_idx:
+                self.add_info(" (object " + self.fmt_hl_obj(grp.idx) + ")")
+            self.add_info("\n")
             self.add_info("  arg1: {a} (0x{a:X})\n\n".format(a = grp.grp_arg1))
             self.add_info("<b>Dialog handlers<b>: {}\n".format(len(grp.acts)))
             fmtga = "  " + fmt_dec(len(grp.acts)) + \


Commit: ba42c34aa5d0c7ffd08fbd5ceadc46189892f941
    https://github.com/scummvm/scummvm-tools/commit/ba42c34aa5d0c7ffd08fbd5ceadc46189892f941
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Refactor GUI to separate file

Changed paths:
  A engines/petka/tkguibrowser.py
    engines/petka/p12explore.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 1b89b9ff8..67182b8df 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -1,26 +1,22 @@
 #! /usr/bin/env python3
 # -*- coding: utf-8 -*-
 
-# romiq.kh at gmail.com, 2014
+# romiq.kh at gmail.com, 2015
 
 import sys, os
 import tkinter
-from tkinter import ttk, font, filedialog, messagebox
-from idlelib.WidgetRedirector import WidgetRedirector
+from tkinter import filedialog, messagebox
 import traceback
-import urllib.parse
-import math
 import webbrowser
 
+from tkguibrowser import TkBrowser, hlesc, cesc, fmt_hl, fmt_hl_len, fmt_arg, \
+    fmt_dec, fmt_dec_len
+
 # Image processing
 try:
     from PIL import Image
 except ImportError:
     Image = None
-try:
-    from PIL import ImageTk
-except ImportError:
-    ImageTk = None
 
 # Translations
 try:
@@ -34,40 +30,6 @@ APPNAME = "Petka Explorer"
 VERSION = "v0.3i 2015-01-19"
 HOME_URLS = ["http://petka-vich.com/petkaexplorer/", 
             "https://bitbucket.org/romiq/p12simtran"]
-
-def hlesc(value):
-    if value is None:
-        return "None"
-    return value.replace("\\", "\\\\").replace("<", "\\<").replace(">", "\\>")
-
-def cesc(value):
-    return value.replace("\\", "\\\\").replace("\"", "\\\"")
-
-def fmt_hl(loc, desc):
-    return "<a href=\"{}\">{}</a>".format(loc, desc)
-
-def fmt_hl_len(loc, desc, ln):
-    sz = max(ln - len(desc), 0)
-    return " "*sz + fmt_hl(loc, desc)
-
-def fmt_arg(value):
-    if value < 10:
-        return "{}".format(value)
-    elif value == 0xffff:
-        return "-1"
-    else:
-        return "0x{:X}".format(value)
-    
-def fmt_dec(value, add = 0):
-    return "{{:{}}}".format(fmt_dec_len(value, add))
-        
-def fmt_dec_len(value, add = 0):
-    if value == 0:
-        d = 1
-    else:
-        d = int(math.log10(value)) + 1
-    d += add
-    return d
     
 def translit(text):
     ru = "абвгдеёзийклмнопрстуфхъыьэАБВГДЕЁЗИЙКЛМНОПРСТУФХЪЫЬЭ"
@@ -103,79 +65,13 @@ def translit(text):
         ret = ret.upper()
     return ret
     
-# thanx to http://effbot.org/zone/tkinter-text-hyperlink.htm
-class HyperlinkManager:
-    def __init__(self, text):
-        self.text = text
-        self.text.tag_config("hyper", foreground = "blue", underline = 1)
-        self.text.tag_bind("hyper", "<Enter>", self._enter)
-        self.text.tag_bind("hyper", "<Leave>", self._leave)
-        self.text.tag_bind("hyper", "<Button-1>", self._click)
-        bold_font = font.Font(text, self.text.cget("font"))
-        bold_font.configure(weight = "bold")
-        self.text.tag_config("bold", font = bold_font)
-        italic_font = font.Font(text, self.text.cget("font"))
-        italic_font.configure(slant = "italic")
-        self.text.tag_config("italic", font = italic_font)
-        self.text.tag_config("underline", underline = 1)
-        self.reset()
-
-    def reset(self):
-    	self.links = {}
-    	self.colors = []
-    	self.bgs = []
-
-    def add(self, action):
-        # add an action to the manager.  returns tags to use in
-        # associated text widget
-        tag = "hyper-{}".format(len(self.links))
-        self.links[tag] = action
-        return "hyper", tag
-
-    def color(self, color):
-        tag = "color-{}".format(color)
-        if tag not in self.colors:
-            self.colors.append(tag)
-            self.text.tag_config(tag, foreground = color)
-            self.text.tag_raise("hyper")
-        return (tag,)
-
-    def bg(self, color):
-        tag = "bg-{}".format(color)
-        if tag not in self.bgs:
-            self.bgs.append(tag)
-            self.text.tag_config(tag, background = color)
-            self.text.tag_raise("hyper")
-        return (tag,)
-
-    def _enter(self, event):
-        self.text.config(cursor = "hand2")
-
-    def _leave(self, event):
-        self.text.config(cursor = "")
-
-    def _click(self, event):
-        for tag in self.text.tag_names(tkinter.CURRENT):
-            if tag[:6] == "hyper-":
-                self.links[tag]()
-                return
 
-# thanx http://tkinter.unpythonic.net/wiki/ReadOnlyText
-class ReadOnlyText(tkinter.Text):
-    def __init__(self, *args, **kwargs):
-        tkinter.Text.__init__(self, *args, **kwargs)
-        self.redirector = WidgetRedirector(self)
-        self.insert = \
-            self.redirector.register("insert", lambda *args, **kw: "break")
-        self.delete = \
-            self.redirector.register("delete", lambda *args, **kw: "break")
-
-class App(tkinter.Frame):
+class App(TkBrowser):
     def __init__(self, master):
-        tkinter.Frame.__init__(self, master)
-        master.title(APPNAME)
-        self.pack(fill = tkinter.BOTH, expand = 1)
-        self.pad = None
+        super().__init__(master)            
+                
+    def init_gui(self):
+        self.master.title(APPNAME)
         self.clear_data()
         # path
         if hasattr(sys, 'frozen'):
@@ -183,27 +79,7 @@ class App(tkinter.Frame):
         else:
             self.app_path = __file__
         self.app_path = os.path.abspath(os.path.dirname(self.app_path))
-        self.start_act = []
-        # gui
-        self.path_handler = {}
-        self.curr_main = -1 # 0 - frame, 1 - canvas
-        self.curr_path = []
-        self.curr_help = ""
-        self.last_path = [None]
-        self.curr_gui = []
-        self.curr_state = {} # local state for location group
-        self.curr_lb_acts = None
-        self.curr_lb_idx = None
-        self.hist = []
-        self.histf = []
-        self.gl_state = {} # global state until program exit
-        # canvas
-        self.need_update = False
-        self.canv_view_fact = 1
-        self.main_image = tkinter.PhotoImage(width = 1, height = 1)
-        # add on_load handler
-        self.after_idle(self.on_first_display)
-        
+                
     def clear_data(self):
         self.sim = None
         self.last_fn = ""
@@ -216,70 +92,7 @@ class App(tkinter.Frame):
         self.tran = None
            
     def create_widgets(self):
-        ttk.Style().configure("Tool.TButton", width = -1) # minimal width
-        ttk.Style().configure("TLabel", padding = self.pad)
-        ttk.Style().configure('Info.TFrame', background = 'white', \
-            foreground = "black")
-
-        # toolbar
-        self.toolbar = ttk.Frame(self)
-        self.toolbar.pack(fill = tkinter.BOTH)
-        btns = [
-            ["Outline", lambda: self.open_path("")],
-            ["Help", self.on_help],
-            [None, None],
-            ["<-", self.on_back],
-            ["->", self.on_forward],
-        ]
-        for text, cmd in btns:
-            if text is None:
-                frm = ttk.Frame(self.toolbar, width = self.pad, 
-                    height = self.pad)
-                frm.pack(side = tkinter.LEFT)
-                continue            
-            btn = ttk.Button(self.toolbar, text = text, \
-                style = "Tool.TButton", command = cmd)
-            btn.pack(side = tkinter.LEFT)
-        frm = ttk.Frame(self.toolbar, width = self.pad, height = self.pad)
-        frm.pack(side = tkinter.LEFT)
-        
-        # main panel
-        self.pan_main = ttk.PanedWindow(self, orient = tkinter.HORIZONTAL)
-        self.pan_main.pack(fill = tkinter.BOTH, expand = 1)
-        
-        # leftpanel
-        self.frm_left = ttk.Frame(self.pan_main)
-        self.pan_main.add(self.frm_left)
-        # main view
-        self.frm_view = ttk.Frame(self.pan_main)
-        self.pan_main.add(self.frm_view)
-        self.frm_view.grid_rowconfigure(0, weight = 1)
-        self.frm_view.grid_columnconfigure(0, weight = 1)
-        self.scr_view_x = ttk.Scrollbar(self.frm_view, 
-            orient = tkinter.HORIZONTAL)
-        self.scr_view_x.grid(row = 1, column = 0, \
-            sticky = tkinter.E + tkinter.W)
-        self.scr_view_y = ttk.Scrollbar(self.frm_view)
-        self.scr_view_y.grid(row = 0, column = 1, sticky = \
-            tkinter.N + tkinter.S)
-        # canvas
-        self.canv_view = tkinter.Canvas(self.frm_view, height = 150, 
-            bd = 0, highlightthickness = 0, 
-            scrollregion = (0, 0, 50, 50),
-            )
-        # don't forget
-        #   canvas.config(scrollregion=(left, top, right, bottom))
-        self.canv_view.bind('<Configure>', self.on_resize_view)
-        self.canv_view.bind('<ButtonPress-1>', self.on_mouse_view)
-        
-        # text
-        self.text_view = ReadOnlyText(self.frm_view,
-            highlightthickness = 0,
-            )
-        self.text_hl = HyperlinkManager(self.text_view)
-        self.text_view.bind('<Configure>', self.on_resize_view)
-        
-        
+        super().create_widgets()
         def desc_def(aname, name, lst = {}):
             def desc(path):
                 if len(path) > 1:
@@ -288,7 +101,6 @@ class App(tkinter.Frame):
                 else:
                     return aname
             return desc
-        
         # bind path handlers
         self.path_handler["parts"] = [self.path_parts, self.desc_parts]
         self.path_handler["res"] = [self.path_res, self.desc_res]
@@ -353,8 +165,10 @@ class App(tkinter.Frame):
                     break
         if repath:
             self.open_path(repath)
+        
 
     def create_menu(self):
+        super().create_menu()
         def mkmenupaths(parent, items):                
             for n in items:
                 if n is None:
@@ -365,10 +179,6 @@ class App(tkinter.Frame):
                     parent.add_command(
                             command = cmd(n),
                             label = self.desc_path(n))
-
-        self.menubar = tkinter.Menu(self.master)
-        self.master.configure(menu = self.menubar)
-
         self.menufile = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menufile,
                 label = "File")
@@ -440,431 +250,6 @@ class App(tkinter.Frame):
         helpnav = ["/help/index", None, "/support", "/info", "/about"]
         mkmenupaths(self.menuhelp, helpnav)
 
-    def update_after(self):
-        if not self.need_update:
-            self.after_idle(self.on_idle)
-            self.need_update = True
-
-    def on_idle(self):
-        self.need_update = False
-        self.update_canvas()
-
-    def on_first_display(self):
-        fnt = font.Font()
-        try:
-            self.pad = fnt.measure(":")
-        except:
-            self.pad = 5
-        self.create_widgets()
-        self.create_menu()
-
-    def on_exit(self):
-        self.master.destroy()
-
-    def on_mouse_view(self, event):
-        self.update_after()
-        
-    def on_resize_view(self, event):
-        self.update_after()
- 
-    def parse_path(self, loc):
-        if isinstance(loc, str):
-            path = []
-            if loc[:1] == "/":
-                loc = loc[1:]
-            if loc != "":
-                for item in loc.split("/"):
-                    try:
-                        path.append(int(item, 10))
-                    except:
-                        path.append(item)
-        else:
-            path = loc
-        path = tuple(path)
-        while path[-1:] == ("",):
-            path = path[:-1]
-        return path    
- 
-    def open_http(self, path):
-        if path not in HOME_URLS:
-            if not messagebox.askokcancel(parent = self, title = "Visit URL?", 
-                message = "Would you like to open external URL:\n" + path + 
-                " ?"): return
-        webbrowser.open(path)
- 
-    def open_path(self, loc, withhist = True):
-        path = self.parse_path(loc)        
-        if withhist:
-            self.hist.append([path])
-            self.histf = []
-        print("DEBUG: Open", path)
-        # set title
-        capt = APPNAME
-        try:
-            if path == ("about",):
-                capt = APPNAME + " - " + VERSION
-            else:
-                capt = APPNAME + " - " + self.desc_path(path)
-        except:
-            pass
-        self.master.title(capt)
-        self.curr_path = path
-        if len(path) > 0:
-            self.curr_help = path[0]
-        else:
-            self.curr_help = ""
-        if len(path) > 0:
-            if path[0] in self.path_handler:
-                return self.path_handler[path[0]][0](path)
-        return self.path_default(path)
-
-    def desc_path(self, loc):
-        path = self.parse_path(loc)
-        if len(path) > 0:
-            if path[0] in self.path_handler:
-                desc = self.path_handler[path[0]][1]
-                if callable(desc):
-                    return desc(path)
-                elif desc:
-                    return desc
-        return self.desc_default(path)
-
-    def update_canvas(self):
-        if self.curr_main == 0:          
-            return
-        # draw grahics
-        c = self.canv_view
-        c.delete(tkinter.ALL)
-        if self.sim is None: return
-
-        w = self.canv_view.winfo_width() 
-        h = self.canv_view.winfo_height()
-        if (w == 0) or (h == 0): 
-            return
-        
-        scale = 0
-
-        # Preview image
-        if not isinstance(self.main_image, tkinter.PhotoImage):
-            mw, mh = self.main_image.size
-            if scale == 0: # Fit
-                try:
-                    psc = w / h
-                    isc = mw / mh
-                    if psc < isc:
-                        fact = w / mw
-                    else:
-                        fact = h / mh
-                except:
-                    fact = 1.0
-            else:
-                fact = scale
-            pw = int(mw * fact)
-            ph = int(mh * fact)
-            img = self.main_image.resize((pw, ph), Image.ANTIALIAS)
-            self.canv_image = ImageTk.PhotoImage(img)
-        else:
-            mw = self.main_image.width()
-            mh = self.main_image.height()
-            if scale == 0: # Fit
-                try:
-                    psc = w / h
-                    isc = mw / mh
-                    if psc < isc:
-                        if w > mw:
-                            fact = w // mw
-                        else:
-                            fact = -mw // w
-                    else:
-                        if h > mh:
-                            fact = h // mh
-                        else:
-                            fact = -mh // h
-                except:
-                    fact = 1
-            else:
-                fact = scale
-            self.canv_image = self.main_image.copy()
-            if fact > 0:
-                self.canv_image = self.canv_image.zoom(fact)
-            else:
-                self.canv_image = self.canv_image.subsample(-fact)
-            self.canv_image_fact = fact
-
-            # place on canvas
-            if fact > 0:
-                pw = mw * fact
-                ph = mh * fact
-            else:
-                pw = mw // -fact
-                ph = mh // -fact
-
-        cw = max(pw, w)
-        ch = max(ph, h)
-        c.config(scrollregion = (0, 0, cw - 2, ch - 2))
-        #print("Place c %d %d, p %d %d" % (cw, ch, w, h))
-        c.create_image(cw // 2, ch // 2, image = self.canv_image)
-       
-    def make_image(self, imgobj):
-        if imgobj.image is not None:
-            return imgobj.image
-        width = imgobj.width
-        height = imgobj.height
-        data = imgobj.rgb
-        # create P6
-        phdr = ("P6\n{} {}\n255\n".format(width, height))
-        rawlen = width * height * 3 # RGB
-        #phdr = ("P5\n{} {}\n255\n".format(width, height))
-        #rawlen = width * height
-        phdr = phdr.encode("UTF-8")
-
-        if len(data) > rawlen:
-            # truncate
-            pdata = data[:rawlen]
-        if len(data) < rawlen:
-            # fill gap
-            gap = bytearray()
-            data += b"\xff" * (rawlen - len(data))
-        p = bytearray(phdr)
-        # fix UTF-8 issue
-        for ch in data:
-            if ch > 0x7f:
-                p += bytes((0b11000000 |\
-                    ch >> 6, 0b10000000 |\
-                    (ch & 0b00111111)))               
-            else:
-                p += bytes((ch,))
-        image = tkinter.PhotoImage(width = width, height = height, \
-            data = bytes(p))
-        return image                
-   
-    def update_gui(self, text = "<Undefined>"):
-        self.last_path = self.curr_path
-        # cleanup
-        for item in self.curr_gui:
-            item()
-        self.curr_gui = []
-        self.curr_state = {} # save state across moves
-        # left listbox
-        lab = tkinter.Label(self.frm_left, text = text)
-        lab.pack()
-        frm_lb = ttk.Frame(self.frm_left)
-        frm_lb.pack(fill = tkinter.BOTH, expand = 1)
-        frm_lb.grid_rowconfigure(0, weight = 1)
-        frm_lb.grid_columnconfigure(0, weight = 1)
-        scr_lb_x = ttk.Scrollbar(frm_lb, orient = tkinter.HORIZONTAL)
-        scr_lb_x.grid(row = 1, column = 0, sticky = tkinter.E + tkinter.W)
-        scr_lb_y = ttk.Scrollbar(frm_lb)
-        scr_lb_y.grid(row = 0, column = 1, sticky = tkinter.N + tkinter.S)
-        frmlbpad = ttk.Frame(frm_lb, borderwidth = self.pad)
-        lb = tkinter.Listbox(frm_lb,
-            highlightthickness = 0,
-            xscrollcommand = scr_lb_x.set,
-            yscrollcommand = scr_lb_y.set)
-        lb.grid(row = 0, column = 0, \
-            sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
-        scr_lb_x.config(command = lb.xview)
-        scr_lb_y.config(command = lb.yview)
-        self.curr_gui.append(lambda:lb.grid_remove())
-        self.curr_gui.append(lambda:lab.pack_forget())
-        self.curr_gui.append(lambda:frm_lb.pack_forget())
-        lb.bind("<Double-Button-1>", self.on_left_listbox)
-        lb.bind("<Return>", self.on_left_listbox)
-        # actions on listbox
-        self.curr_lb = lb
-        self.curr_lb_acts = []
-        self.curr_lb_idx = {}
-
-    def switch_view(self, main):
-        # main view
-        if main == self.curr_main: return
-        last = self.curr_main
-        self.curr_main = main
-        rw = None
-        rh = None
-        if main == 0:
-            self.canv_view.delete(tkinter.ALL)
-            self.canv_view.grid_forget()
-            self.text_view.grid(row = 0, column = 0, \
-                sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
-            self.text_view.configure(
-                xscrollcommand = self.scr_view_x.set,
-                yscrollcommand = self.scr_view_y.set
-            )
-            self.scr_view_x.config(command = self.text_view.xview)
-            self.scr_view_y.config(command = self.text_view.yview)
-        else:
-        
-            if last == 0:
-                rw = self.text_view.winfo_width()
-                rh = self.text_view.winfo_height()
-            self.canv_view.delete(tkinter.ALL)
-            self.text_view.grid_forget()
-            self.canv_view.grid(row = 0, column = 0, \
-                sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
-            self.canv_view.configure(
-                xscrollcommand = self.scr_view_x.set,
-                yscrollcommand = self.scr_view_y.set
-            )
-            self.scr_view_x.config(command = self.canv_view.xview)
-            self.scr_view_y.config(command = self.canv_view.yview)
-            if rh:
-                print(rh)
-                self.canv_view.height = rh
-                print(self.canv_view.winfo_height())
-
-    def clear_info(self):
-        self.text_view.delete(0.0, tkinter.END)
-
-    def add_info(self, text):
-        mode = 0 # 0 - normal, 1 - tag
-        curr_tag = None
-        curr_text = ""
-        tags = []
-        esc = False
-        for ch in text:
-            if mode == 0:
-                if esc:
-                    curr_text += ch
-                    esc = False
-                else:
-                    if ch == "\\":
-                        esc = True
-                    elif ch == "<":
-                        mode = 1
-                        curr_tag = ""
-                    else:
-                        curr_text += ch
-            else:
-                if ch == ">":
-                    if len(curr_text) > 0:                    
-                        self.text_view.insert(tkinter.INSERT, curr_text, \
-                            tuple(reversed([x for x in tags for x in x])))
-                    if curr_tag[:7] == "a href=":
-                        ref = curr_tag[7:]
-                        if ref[:1] == "\"":
-                            ref = ref[1:]
-                        if ref[-1:] == "\"":
-                            ref = ref[:-1]
-                        def make_cb(path):
-                            def cb():
-                                if path[:5] == "http:" or path[:6] == "https:":
-                                    return self.open_http(path)
-                                return self.open_path(path)
-                            return cb
-                        tags.append(self.text_hl.add(make_cb(ref)))
-                    elif curr_tag[:11] == "font color=":
-                        ref = curr_tag[11:]
-                        if ref[:1] == "\"":
-                            ref = ref[1:]
-                        if ref[-1:] == "\"":
-                            ref = ref[:-1]
-                        tags.append(self.text_hl.color(ref))
-                    elif curr_tag[:8] == "font bg=":
-                        ref = curr_tag[8:]
-                        if ref[:1] == "\"":
-                            ref = ref[1:]
-                        if ref[-1:] == "\"":
-                            ref = ref[:-1]
-                        tags.append(self.text_hl.bg(ref))
-                    elif curr_tag == "b":
-                        tags.append(["bold"])
-                    elif curr_tag == "i":
-                        tags.append(["italic"])
-                    elif curr_tag == "u":
-                        tags.append(["underline"])
-                    elif curr_tag[:1] == "/":
-                        tags = tags[:-1]
-                    curr_text = ""
-                    mode = 0
-                else:
-                    curr_tag += ch
-        if len(curr_text) > 0: 
-            self.text_view.insert(tkinter.INSERT, curr_text, \
-                tuple(reversed([x for x in tags for x in x])))
-        
-    def insert_lb_act(self, name, act, key = None):
-        if key is not None:
-            self.curr_lb_idx[key] = len(self.curr_lb_acts)
-        self.curr_lb_acts.append((name, act))
-        if name == "-" and act is None:
-            self.curr_lb.insert(tkinter.END, "")
-        else:
-            self.curr_lb.insert(tkinter.END, " " + name)
-
-    def select_lb_item(self, key):
-        idx = self.curr_lb_idx.get(key, None)
-        need = (idx is not None)
-        idxs = "{}".format(idx)
-        for sel in self.curr_lb.curselection():
-            if sel == idxs:
-                need = False
-            else:
-                self.curr_lb.selection_clear(sel)
-        if need:
-            self.curr_lb.selection_set(idxs)
-        if idx is not None:
-            self.curr_lb.see(idxs)
-            
-    def on_left_listbox(self, event):
-        def currsel():
-            try:
-                num = self.curr_lb.curselection()[0]
-                num = int(num)
-            except:
-                return None
-            return num
-
-        if self.curr_lb_acts:
-            act = self.curr_lb_acts[currsel()]
-            if act[1] is not None:
-                self.open_path(act[1])
-
-    def add_toolbtn(self, text, cmd):
-        if text is None:
-            frm = ttk.Frame(self.toolbar, width = self.pad, height = self.pad)
-            frm.pack(side = tkinter.LEFT)
-            self.curr_gui.append(lambda:frm.pack_forget())    
-            return
-        btn = ttk.Button(self.toolbar, text = text, \
-            style = "Tool.TButton", command = cmd)
-        btn.pack(side = tkinter.LEFT)
-        self.curr_gui.append(lambda:btn.pack_forget())
-        return btn
-        
-    def add_toollabel(self, text):
-        lab = ttk.Label(self.toolbar, text = text)
-        lab.pack(side = tkinter.LEFT)
-        self.curr_gui.append(lambda:lab.pack_forget())
-        return lab
-
-    def add_toolgrp(self, label, glkey, items, cbupd):
-        def makecb(v, g):
-            def btncb():
-                self.gl_state[g] = v
-                cbupd()
-            return btncb
-        if label:
-            self.add_toollabel(label)
-        kl = list(items.keys())
-        kl.sort()
-        res = []
-        for k in kl:
-            b = self.add_toolbtn(items[k], makecb(k, glkey))
-            res.append([b, k])
-        return res
-
-    def upd_toolgrp(self, btns, state):
-        for btn, idx in btns:
-            if idx != state and state != -1:
-                btn.config(state = tkinter.NORMAL)
-            else:
-                btn.config(state = tkinter.DISABLED)
-    
-
-    def clear_hist(self):
-        self.hist = self.hist[-1:]
-        self.histf = []
 
     def on_help(self):
         self.open_path(["help", self.curr_help])
@@ -883,6 +268,21 @@ class App(tkinter.Frame):
             self.hist.append(np)
             self.open_path(np[0], False)
 
+    def open_path(self, loc, withhist = True):
+        res = super().open_path(loc, withhist)
+        # set title
+        capt = APPNAME
+        try:
+            if path == ("about",):
+                capt = APPNAME + " - " + VERSION
+            else:
+                capt = APPNAME + " - " + self.desc_path(path)
+        except:
+            pass
+        self.master.title(capt)
+        return res
+        
+
     def _t(self, value, tp):
         if not self.tran: return value
         if tp in self.tran:
diff --git a/engines/petka/tkguibrowser.py b/engines/petka/tkguibrowser.py
new file mode 100644
index 000000000..675d8df2c
--- /dev/null
+++ b/engines/petka/tkguibrowser.py
@@ -0,0 +1,634 @@
+#! /usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# romiq.kh at gmail.com, 2015
+
+import math
+import urllib.parse
+
+import tkinter
+from tkinter import ttk, font, filedialog, messagebox
+from idlelib.WidgetRedirector import WidgetRedirector
+
+try:
+    from PIL import ImageTk
+except ImportError:
+    ImageTk = None
+
+def hlesc(value):
+    if value is None:
+        return "None"
+    return value.replace("\\", "\\\\").replace("<", "\\<").replace(">", "\\>")
+
+def cesc(value):
+    return value.replace("\\", "\\\\").replace("\"", "\\\"")
+
+def fmt_hl(loc, desc):
+    return "<a href=\"{}\">{}</a>".format(loc, desc)
+
+def fmt_hl_len(loc, desc, ln):
+    sz = max(ln - len(desc), 0)
+    return " "*sz + fmt_hl(loc, desc)
+
+def fmt_arg(value):
+    if value < 10:
+        return "{}".format(value)
+    elif value == 0xffff:
+        return "-1"
+    else:
+        return "0x{:X}".format(value)
+    
+def fmt_dec(value, add = 0):
+    return "{{:{}}}".format(fmt_dec_len(value, add))
+        
+def fmt_dec_len(value, add = 0):
+    if value == 0:
+        d = 1
+    else:
+        d = int(math.log10(value)) + 1
+    d += add
+    return d
+
+# thanx to http://effbot.org/zone/tkinter-text-hyperlink.htm
+class HyperlinkManager:
+    def __init__(self, text):
+        self.text = text
+        self.text.tag_config("hyper", foreground = "blue", underline = 1)
+        self.text.tag_bind("hyper", "<Enter>", self._enter)
+        self.text.tag_bind("hyper", "<Leave>", self._leave)
+        self.text.tag_bind("hyper", "<Button-1>", self._click)
+        bold_font = font.Font(text, self.text.cget("font"))
+        bold_font.configure(weight = "bold")
+        self.text.tag_config("bold", font = bold_font)
+        italic_font = font.Font(text, self.text.cget("font"))
+        italic_font.configure(slant = "italic")
+        self.text.tag_config("italic", font = italic_font)
+        self.text.tag_config("underline", underline = 1)
+        self.reset()
+
+    def reset(self):
+    	self.links = {}
+    	self.colors = []
+    	self.bgs = []
+
+    def add(self, action):
+        # add an action to the manager.  returns tags to use in
+        # associated text widget
+        tag = "hyper-{}".format(len(self.links))
+        self.links[tag] = action
+        return "hyper", tag
+
+    def color(self, color):
+        tag = "color-{}".format(color)
+        if tag not in self.colors:
+            self.colors.append(tag)
+            self.text.tag_config(tag, foreground = color)
+            self.text.tag_raise("hyper")
+        return (tag,)
+
+    def bg(self, color):
+        tag = "bg-{}".format(color)
+        if tag not in self.bgs:
+            self.bgs.append(tag)
+            self.text.tag_config(tag, background = color)
+            self.text.tag_raise("hyper")
+        return (tag,)
+
+    def _enter(self, event):
+        self.text.config(cursor = "hand2")
+
+    def _leave(self, event):
+        self.text.config(cursor = "")
+
+    def _click(self, event):
+        for tag in self.text.tag_names(tkinter.CURRENT):
+            if tag[:6] == "hyper-":
+                self.links[tag]()
+                return
+
+# thanx http://tkinter.unpythonic.net/wiki/ReadOnlyText
+class ReadOnlyText(tkinter.Text):
+    def __init__(self, *args, **kwargs):
+        tkinter.Text.__init__(self, *args, **kwargs)
+        self.redirector = WidgetRedirector(self)
+        self.insert = \
+            self.redirector.register("insert", lambda *args, **kw: "break")
+        self.delete = \
+            self.redirector.register("delete", lambda *args, **kw: "break")
+
+class TkBrowser(tkinter.Frame):
+    def __init__(self, master):
+        tkinter.Frame.__init__(self, master)
+        self.pack(fill = tkinter.BOTH, expand = 1)        
+        self.pad = None
+
+        # gui
+        self.path_handler = {}
+        self.curr_main = -1 # 0 - frame, 1 - canvas
+        self.curr_path = []
+        self.curr_help = ""
+        self.last_path = [None]
+        self.curr_gui = []
+        self.curr_state = {} # local state for location group
+        self.curr_lb_acts = None
+        self.curr_lb_idx = None
+        self.hist = []
+        self.histf = []
+        self.gl_state = {} # global state until program exit
+        self.start_act = []
+        self.init_gui() # init custom gui data
+
+        # canvas
+        self.need_update = False
+        self.canv_view_fact = 1
+        self.main_image = tkinter.PhotoImage(width = 1, height = 1)
+        # add on_load handler
+        self.after_idle(self.on_first_display)
+    
+    def init_gui(self):
+        pass    
+
+    def update_after(self):
+        if not self.need_update:
+            self.after_idle(self.on_idle)
+            self.need_update = True
+
+    def on_idle(self):
+        self.need_update = False
+        self.update_canvas()
+
+    def on_first_display(self):
+        fnt = font.Font()
+        try:
+            self.pad = fnt.measure(":")
+        except:
+            self.pad = 5
+        self.create_widgets()
+        self.create_menu()
+
+    def create_widgets(self):
+        ttk.Style().configure("Tool.TButton", width = -1) # minimal width
+        ttk.Style().configure("TLabel", padding = self.pad)
+        ttk.Style().configure('Info.TFrame', background = 'white', \
+            foreground = "black")
+
+        # toolbar
+        self.toolbar = ttk.Frame(self)
+        self.toolbar.pack(fill = tkinter.BOTH)
+        btns = [
+            ["Outline", lambda: self.open_path("")],
+            ["Help", self.on_help],
+            [None, None],
+            ["<-", self.on_back],
+            ["->", self.on_forward],
+        ]
+        for text, cmd in btns:
+            if text is None:
+                frm = ttk.Frame(self.toolbar, width = self.pad, 
+                    height = self.pad)
+                frm.pack(side = tkinter.LEFT)
+                continue            
+            btn = ttk.Button(self.toolbar, text = text, \
+                style = "Tool.TButton", command = cmd)
+            btn.pack(side = tkinter.LEFT)
+        frm = ttk.Frame(self.toolbar, width = self.pad, height = self.pad)
+        frm.pack(side = tkinter.LEFT)
+        
+        # main panel
+        self.pan_main = ttk.PanedWindow(self, orient = tkinter.HORIZONTAL)
+        self.pan_main.pack(fill = tkinter.BOTH, expand = 1)
+        
+        # leftpanel
+        self.frm_left = ttk.Frame(self.pan_main)
+        self.pan_main.add(self.frm_left)
+        # main view
+        self.frm_view = ttk.Frame(self.pan_main)
+        self.pan_main.add(self.frm_view)
+        self.frm_view.grid_rowconfigure(0, weight = 1)
+        self.frm_view.grid_columnconfigure(0, weight = 1)
+        self.scr_view_x = ttk.Scrollbar(self.frm_view, 
+            orient = tkinter.HORIZONTAL)
+        self.scr_view_x.grid(row = 1, column = 0, \
+            sticky = tkinter.E + tkinter.W)
+        self.scr_view_y = ttk.Scrollbar(self.frm_view)
+        self.scr_view_y.grid(row = 0, column = 1, sticky = \
+            tkinter.N + tkinter.S)
+        # canvas
+        self.canv_view = tkinter.Canvas(self.frm_view, height = 150, 
+            bd = 0, highlightthickness = 0, 
+            scrollregion = (0, 0, 50, 50),
+            )
+        # don't forget
+        #   canvas.config(scrollregion=(left, top, right, bottom))
+        self.canv_view.bind('<Configure>', self.on_resize_view)
+        self.canv_view.bind('<ButtonPress-1>', self.on_mouse_view)
+        
+        # text
+        self.text_view = ReadOnlyText(self.frm_view,
+            highlightthickness = 0,
+            )
+        self.text_hl = HyperlinkManager(self.text_view)
+        self.text_view.bind('<Configure>', self.on_resize_view)
+        
+    def create_menu(self):
+        self.menubar = tkinter.Menu(self.master)
+        self.master.configure(menu = self.menubar)
+
+    def on_exit(self):
+        self.master.destroy()
+
+    def on_mouse_view(self, event):
+        self.update_after()
+        
+    def on_resize_view(self, event):
+        self.update_after()
+ 
+    def parse_path(self, loc):
+        if isinstance(loc, str):
+            path = []
+            if loc[:1] == "/":
+                loc = loc[1:]
+            if loc != "":
+                for item in loc.split("/"):
+                    try:
+                        path.append(int(item, 10))
+                    except:
+                        path.append(item)
+        else:
+            path = loc
+        path = tuple(path)
+        while path[-1:] == ("",):
+            path = path[:-1]
+        return path    
+ 
+    def open_http(self, path):
+        if path not in HOME_URLS:
+            if not messagebox.askokcancel(parent = self, title = "Visit URL?", 
+                message = "Would you like to open external URL:\n" + path + 
+                " ?"): return
+        webbrowser.open(path)
+ 
+    def open_path(self, loc, withhist = True):
+        path = self.parse_path(loc)        
+        if withhist:
+            self.hist.append([path])
+            self.histf = []
+        print("DEBUG: Open", path)
+        self.curr_path = path
+        if len(path) > 0:
+            self.curr_help = path[0]
+        else:
+            self.curr_help = ""
+        if len(path) > 0:
+            if path[0] in self.path_handler:
+                return self.path_handler[path[0]][0](path)
+        return self.path_default(path)
+
+    def desc_path(self, loc):
+        path = self.parse_path(loc)
+        if len(path) > 0:
+            if path[0] in self.path_handler:
+                desc = self.path_handler[path[0]][1]
+                if callable(desc):
+                    return desc(path)
+                elif desc:
+                    return desc
+        return self.desc_default(path)
+
+    def update_canvas(self):
+        if self.curr_main == 0:          
+            return
+        # draw grahics
+        c = self.canv_view
+        c.delete(tkinter.ALL)
+        if self.sim is None: return
+
+        w = self.canv_view.winfo_width() 
+        h = self.canv_view.winfo_height()
+        if (w == 0) or (h == 0): 
+            return
+        
+        scale = 0
+
+        # Preview image
+        if not isinstance(self.main_image, tkinter.PhotoImage):
+            mw, mh = self.main_image.size
+            if scale == 0: # Fit
+                try:
+                    psc = w / h
+                    isc = mw / mh
+                    if psc < isc:
+                        fact = w / mw
+                    else:
+                        fact = h / mh
+                except:
+                    fact = 1.0
+            else:
+                fact = scale
+            pw = int(mw * fact)
+            ph = int(mh * fact)
+            img = self.main_image.resize((pw, ph), Image.ANTIALIAS)
+            self.canv_image = ImageTk.PhotoImage(img)
+        else:
+            mw = self.main_image.width()
+            mh = self.main_image.height()
+            if scale == 0: # Fit
+                try:
+                    psc = w / h
+                    isc = mw / mh
+                    if psc < isc:
+                        if w > mw:
+                            fact = w // mw
+                        else:
+                            fact = -mw // w
+                    else:
+                        if h > mh:
+                            fact = h // mh
+                        else:
+                            fact = -mh // h
+                except:
+                    fact = 1
+            else:
+                fact = scale
+            self.canv_image = self.main_image.copy()
+            if fact > 0:
+                self.canv_image = self.canv_image.zoom(fact)
+            else:
+                self.canv_image = self.canv_image.subsample(-fact)
+            self.canv_image_fact = fact
+
+            # place on canvas
+            if fact > 0:
+                pw = mw * fact
+                ph = mh * fact
+            else:
+                pw = mw // -fact
+                ph = mh // -fact
+
+        cw = max(pw, w)
+        ch = max(ph, h)
+        c.config(scrollregion = (0, 0, cw - 2, ch - 2))
+        #print("Place c %d %d, p %d %d" % (cw, ch, w, h))
+        c.create_image(cw // 2, ch // 2, image = self.canv_image)
+       
+    def make_image(self, imgobj):
+        if imgobj.image is not None:
+            return imgobj.image
+        width = imgobj.width
+        height = imgobj.height
+        data = imgobj.rgb
+        # create P6
+        phdr = ("P6\n{} {}\n255\n".format(width, height))
+        rawlen = width * height * 3 # RGB
+        #phdr = ("P5\n{} {}\n255\n".format(width, height))
+        #rawlen = width * height
+        phdr = phdr.encode("UTF-8")
+
+        if len(data) > rawlen:
+            # truncate
+            pdata = data[:rawlen]
+        if len(data) < rawlen:
+            # fill gap
+            gap = bytearray()
+            data += b"\xff" * (rawlen - len(data))
+        p = bytearray(phdr)
+        # fix UTF-8 issue
+        for ch in data:
+            if ch > 0x7f:
+                p += bytes((0b11000000 |\
+                    ch >> 6, 0b10000000 |\
+                    (ch & 0b00111111)))               
+            else:
+                p += bytes((ch,))
+        image = tkinter.PhotoImage(width = width, height = height, \
+            data = bytes(p))
+        return image                
+   
+    def update_gui(self, text = "<Undefined>"):
+        self.last_path = self.curr_path
+        # cleanup
+        for item in self.curr_gui:
+            item()
+        self.curr_gui = []
+        self.curr_state = {} # save state across moves
+        # left listbox
+        lab = tkinter.Label(self.frm_left, text = text)
+        lab.pack()
+        frm_lb = ttk.Frame(self.frm_left)
+        frm_lb.pack(fill = tkinter.BOTH, expand = 1)
+        frm_lb.grid_rowconfigure(0, weight = 1)
+        frm_lb.grid_columnconfigure(0, weight = 1)
+        scr_lb_x = ttk.Scrollbar(frm_lb, orient = tkinter.HORIZONTAL)
+        scr_lb_x.grid(row = 1, column = 0, sticky = tkinter.E + tkinter.W)
+        scr_lb_y = ttk.Scrollbar(frm_lb)
+        scr_lb_y.grid(row = 0, column = 1, sticky = tkinter.N + tkinter.S)
+        frmlbpad = ttk.Frame(frm_lb, borderwidth = self.pad)
+        lb = tkinter.Listbox(frm_lb,
+            highlightthickness = 0,
+            xscrollcommand = scr_lb_x.set,
+            yscrollcommand = scr_lb_y.set)
+        lb.grid(row = 0, column = 0, \
+            sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
+        scr_lb_x.config(command = lb.xview)
+        scr_lb_y.config(command = lb.yview)
+        self.curr_gui.append(lambda:lb.grid_remove())
+        self.curr_gui.append(lambda:lab.pack_forget())
+        self.curr_gui.append(lambda:frm_lb.pack_forget())
+        lb.bind("<Double-Button-1>", self.on_left_listbox)
+        lb.bind("<Return>", self.on_left_listbox)
+        # actions on listbox
+        self.curr_lb = lb
+        self.curr_lb_acts = []
+        self.curr_lb_idx = {}
+
+    def switch_view(self, main):
+        # main view
+        if main == self.curr_main: return
+        last = self.curr_main
+        self.curr_main = main
+        rw = None
+        rh = None
+        if main == 0:
+            self.canv_view.delete(tkinter.ALL)
+            self.canv_view.grid_forget()
+            self.text_view.grid(row = 0, column = 0, \
+                sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
+            self.text_view.configure(
+                xscrollcommand = self.scr_view_x.set,
+                yscrollcommand = self.scr_view_y.set
+            )
+            self.scr_view_x.config(command = self.text_view.xview)
+            self.scr_view_y.config(command = self.text_view.yview)
+        else:
+        
+            if last == 0:
+                rw = self.text_view.winfo_width()
+                rh = self.text_view.winfo_height()
+            self.canv_view.delete(tkinter.ALL)
+            self.text_view.grid_forget()
+            self.canv_view.grid(row = 0, column = 0, \
+                sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
+            self.canv_view.configure(
+                xscrollcommand = self.scr_view_x.set,
+                yscrollcommand = self.scr_view_y.set
+            )
+            self.scr_view_x.config(command = self.canv_view.xview)
+            self.scr_view_y.config(command = self.canv_view.yview)
+            if rh:
+                print(rh)
+                self.canv_view.height = rh
+                print(self.canv_view.winfo_height())
+
+    def clear_info(self):
+        self.text_view.delete(0.0, tkinter.END)
+
+    def add_info(self, text):
+        mode = 0 # 0 - normal, 1 - tag
+        curr_tag = None
+        curr_text = ""
+        tags = []
+        esc = False
+        for ch in text:
+            if mode == 0:
+                if esc:
+                    curr_text += ch
+                    esc = False
+                else:
+                    if ch == "\\":
+                        esc = True
+                    elif ch == "<":
+                        mode = 1
+                        curr_tag = ""
+                    else:
+                        curr_text += ch
+            else:
+                if ch == ">":
+                    if len(curr_text) > 0:                    
+                        self.text_view.insert(tkinter.INSERT, curr_text, \
+                            tuple(reversed([x for x in tags for x in x])))
+                    if curr_tag[:7] == "a href=":
+                        ref = curr_tag[7:]
+                        if ref[:1] == "\"":
+                            ref = ref[1:]
+                        if ref[-1:] == "\"":
+                            ref = ref[:-1]
+                        def make_cb(path):
+                            def cb():
+                                if path[:5] == "http:" or path[:6] == "https:":
+                                    return self.open_http(path)
+                                return self.open_path(path)
+                            return cb
+                        tags.append(self.text_hl.add(make_cb(ref)))
+                    elif curr_tag[:11] == "font color=":
+                        ref = curr_tag[11:]
+                        if ref[:1] == "\"":
+                            ref = ref[1:]
+                        if ref[-1:] == "\"":
+                            ref = ref[:-1]
+                        tags.append(self.text_hl.color(ref))
+                    elif curr_tag[:8] == "font bg=":
+                        ref = curr_tag[8:]
+                        if ref[:1] == "\"":
+                            ref = ref[1:]
+                        if ref[-1:] == "\"":
+                            ref = ref[:-1]
+                        tags.append(self.text_hl.bg(ref))
+                    elif curr_tag == "b":
+                        tags.append(["bold"])
+                    elif curr_tag == "i":
+                        tags.append(["italic"])
+                    elif curr_tag == "u":
+                        tags.append(["underline"])
+                    elif curr_tag[:1] == "/":
+                        tags = tags[:-1]
+                    curr_text = ""
+                    mode = 0
+                else:
+                    curr_tag += ch
+        if len(curr_text) > 0: 
+            self.text_view.insert(tkinter.INSERT, curr_text, \
+                tuple(reversed([x for x in tags for x in x])))
+        
+    def insert_lb_act(self, name, act, key = None):
+        if key is not None:
+            self.curr_lb_idx[key] = len(self.curr_lb_acts)
+        self.curr_lb_acts.append((name, act))
+        if name == "-" and act is None:
+            self.curr_lb.insert(tkinter.END, "")
+        else:
+            self.curr_lb.insert(tkinter.END, " " + name)
+
+    def select_lb_item(self, key):
+        idx = self.curr_lb_idx.get(key, None)
+        need = (idx is not None)
+        idxs = "{}".format(idx)
+        for sel in self.curr_lb.curselection():
+            if sel == idxs:
+                need = False
+            else:
+                self.curr_lb.selection_clear(sel)
+        if need:
+            self.curr_lb.selection_set(idxs)
+        if idx is not None:
+            self.curr_lb.see(idxs)
+            
+    def on_left_listbox(self, event):
+        def currsel():
+            try:
+                num = self.curr_lb.curselection()[0]
+                num = int(num)
+            except:
+                return None
+            return num
+
+        if self.curr_lb_acts:
+            act = self.curr_lb_acts[currsel()]
+            if act[1] is not None:
+                self.open_path(act[1])
+
+    def add_toolbtn(self, text, cmd):
+        if text is None:
+            frm = ttk.Frame(self.toolbar, width = self.pad, height = self.pad)
+            frm.pack(side = tkinter.LEFT)
+            self.curr_gui.append(lambda:frm.pack_forget())    
+            return
+        btn = ttk.Button(self.toolbar, text = text, \
+            style = "Tool.TButton", command = cmd)
+        btn.pack(side = tkinter.LEFT)
+        self.curr_gui.append(lambda:btn.pack_forget())
+        return btn
+        
+    def add_toollabel(self, text):
+        lab = ttk.Label(self.toolbar, text = text)
+        lab.pack(side = tkinter.LEFT)
+        self.curr_gui.append(lambda:lab.pack_forget())
+        return lab
+
+    def add_toolgrp(self, label, glkey, items, cbupd):
+        def makecb(v, g):
+            def btncb():
+                self.gl_state[g] = v
+                cbupd()
+            return btncb
+        if label:
+            self.add_toollabel(label)
+        kl = list(items.keys())
+        kl.sort()
+        res = []
+        for k in kl:
+            b = self.add_toolbtn(items[k], makecb(k, glkey))
+            res.append([b, k])
+        return res
+
+    def upd_toolgrp(self, btns, state):
+        for btn, idx in btns:
+            if idx != state and state != -1:
+                btn.config(state = tkinter.NORMAL)
+            else:
+                btn.config(state = tkinter.DISABLED)
+    
+
+    def clear_hist(self):
+        self.hist = self.hist[-1:]
+        self.histf = []
+


Commit: b6ef152bc3f855efd440a798b662bfb42b17d3e1
    https://github.com/scummvm/scummvm-tools/commit/b6ef152bc3f855efd440a798b662bfb42b17d3e1
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: refactor gui

Changed paths:
  A engines/petka/testtkgui.py
    engines/petka/p12explore.py
    engines/petka/tkguibrowser.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 67182b8df..dae941c61 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -4,20 +4,14 @@
 # romiq.kh at gmail.com, 2015
 
 import sys, os
+import urllib.parse
 import tkinter
 from tkinter import filedialog, messagebox
 import traceback
-import webbrowser
 
 from tkguibrowser import TkBrowser, hlesc, cesc, fmt_hl, fmt_hl_len, fmt_arg, \
     fmt_dec, fmt_dec_len
 
-# Image processing
-try:
-    from PIL import Image
-except ImportError:
-    Image = None
-
 # Translations
 try:
     import polib
@@ -27,7 +21,7 @@ except ImportError:
 import petka
 
 APPNAME = "Petka Explorer"
-VERSION = "v0.3i 2015-01-19"
+VERSION = "v0.4 2015-06-20"
 HOME_URLS = ["http://petka-vich.com/petkaexplorer/", 
             "https://bitbucket.org/romiq/p12simtran"]
     
@@ -164,8 +158,7 @@ class App(TkBrowser):
                     repath = ""
                     break
         if repath:
-            self.open_path(repath)
-        
+            self.open_path(repath)       
 
     def create_menu(self):
         super().create_menu()
@@ -251,23 +244,6 @@ class App(TkBrowser):
         mkmenupaths(self.menuhelp, helpnav)
 
 
-    def on_help(self):
-        self.open_path(["help", self.curr_help])
-
-    def on_back(self):
-        if len(self.hist) > 1:
-            np = self.hist[-2:-1][0]
-            self.histf = self.hist[-1:] + self.histf
-            self.hist = self.hist[:-1]
-            self.open_path(np[0], False)
-
-    def on_forward(self):
-        if len(self.histf) > 0:
-            np = self.histf[0]
-            self.histf = self.histf[1:]
-            self.hist.append(np)
-            self.open_path(np[0], False)
-
     def open_path(self, loc, withhist = True):
         res = super().open_path(loc, withhist)
         # set title
@@ -282,6 +258,8 @@ class App(TkBrowser):
         self.master.title(capt)
         return res
         
+    def on_help(self):
+        self.open_path(["help", self.curr_help])
 
     def _t(self, value, tp):
         if not self.tran: return value
diff --git a/engines/petka/testtkgui.py b/engines/petka/testtkgui.py
new file mode 100755
index 000000000..6250041fb
--- /dev/null
+++ b/engines/petka/testtkgui.py
@@ -0,0 +1,68 @@
+#! /usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# romiq.kh at gmail.com, 2015
+
+import sys, os
+import traceback
+import tkinter
+
+from tkguibrowser import TkBrowser, hlesc, cesc, fmt_hl, fmt_hl_len, fmt_arg, \
+    fmt_dec, fmt_dec_len
+
+APPNAME = "Test tkinter gui browser"
+VERSION = "v0.1 2015-06-20"
+
+class App(TkBrowser):
+    def __init__(self, master):
+        super().__init__(master)            
+                
+    def init_gui(self):
+        self.master.title(APPNAME)
+        # path
+        if hasattr(sys, 'frozen'):
+            self.app_path = sys.executable
+        else:
+            self.app_path = __file__
+        self.app_path = os.path.abspath(os.path.dirname(self.app_path))
+                
+    def create_widgets(self):
+        super().create_widgets()
+        self.open_path("/")
+
+
+    def create_menu(self):
+        super().create_menu()
+        self.menufile = tkinter.Menu(self.master, tearoff = 0)
+        self.menubar.add_cascade(menu = self.menufile,
+                label = "File")
+        self.menufile.add_separator()
+        self.menufile.add_command(
+                command = self.on_exit,
+                label = "Quit")    
+
+def main():
+    root = tkinter.Tk()
+    app = App(master = root)
+    argv = sys.argv[1:]
+    while len(argv) > 0:
+        if argv[0] == "-d": # open data
+            app.start_act.append(["load", argv[1]])
+            argv = argv[2:]
+        elif argv[0] == "-s": # open str file
+            app.start_act.append(["str", argv[1]])
+            argv = argv[2:]
+        elif argv[0] == "-sd": # open savex.dat file
+            app.start_act.append(["savedat", argv[1]])
+            argv = argv[2:]
+        elif argv[0] == "-t": # open translation
+            app.start_act.append(["tran", argv[1]])
+            argv = argv[2:]
+        else:
+            app.start_act.append(["open", argv[0]])
+            argv = argv[1:]
+    app.mainloop()
+
+    
+if __name__ == "__main__":
+    main()
diff --git a/engines/petka/tkguibrowser.py b/engines/petka/tkguibrowser.py
index 675d8df2c..0aa376226 100644
--- a/engines/petka/tkguibrowser.py
+++ b/engines/petka/tkguibrowser.py
@@ -4,12 +4,17 @@
 # romiq.kh at gmail.com, 2015
 
 import math
-import urllib.parse
 
 import tkinter
 from tkinter import ttk, font, filedialog, messagebox
 from idlelib.WidgetRedirector import WidgetRedirector
 
+# Image processing
+try:
+    from PIL import Image
+except ImportError:
+    Image = None
+
 try:
     from PIL import ImageTk
 except ImportError:
@@ -166,6 +171,24 @@ class TkBrowser(tkinter.Frame):
         self.create_widgets()
         self.create_menu()
 
+    def on_help(self):
+        pass
+
+    def on_back(self):
+        if len(self.hist) > 1:
+            np = self.hist[-2:-1][0]
+            self.histf = self.hist[-1:] + self.histf
+            self.hist = self.hist[:-1]
+            self.open_path(np[0], False)
+
+    def on_forward(self):
+        if len(self.histf) > 0:
+            np = self.histf[0]
+            self.histf = self.histf[1:]
+            self.hist.append(np)
+            self.open_path(np[0], False)
+
+
     def create_widgets(self):
         ttk.Style().configure("Tool.TButton", width = -1) # minimal width
         ttk.Style().configure("TLabel", padding = self.pad)
@@ -296,12 +319,11 @@ class TkBrowser(tkinter.Frame):
         return self.desc_default(path)
 
     def update_canvas(self):
-        if self.curr_main == 0:          
+        if self.curr_main == 0:
             return
         # draw grahics
         c = self.canv_view
         c.delete(tkinter.ALL)
-        if self.sim is None: return
 
         w = self.canv_view.winfo_width() 
         h = self.canv_view.winfo_height()
@@ -627,8 +649,14 @@ class TkBrowser(tkinter.Frame):
             else:
                 btn.config(state = tkinter.DISABLED)
     
-
     def clear_hist(self):
         self.hist = self.hist[-1:]
         self.histf = []
 
+    def path_default(self, path):
+        self.switch_view(0)
+        self.clear_info()
+        self.add_info("Open path\n\n" + str(path))
+        
+        
+


Commit: 950acde83a83fc6d203ba99ec305e24ab9db43f9
    https://github.com/scummvm/scummvm-tools/commit/950acde83a83fc6d203ba99ec305e24ab9db43f9
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Fixes for gui

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py
    engines/petka/testtkgui.py
    engines/petka/tkguibrowser.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 66b240d52..cb2b466f3 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -1,5 +1,9 @@
 Что нового
 ==========
+2015-06-20 версия 0.4
+---------------------
+Графический интерфейс выделен в отдельный модуль
+
 2015-01-19 версия 0.3i
 ----------------------
 Исправлено отображение сцен при отсутствии ссылок на объекты
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index dae941c61..70b4b08ed 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -58,11 +58,11 @@ def translit(text):
     if allcaps:
         ret = ret.upper()
     return ret
-    
 
 class App(TkBrowser):
+
     def __init__(self, master):
-        super().__init__(master)            
+        super().__init__(master)
                 
     def init_gui(self):
         self.master.title(APPNAME)
@@ -122,7 +122,6 @@ class App(TkBrowser):
             desc_def("Stores", "Store")]
         self.path_handler["files"] = [self.path_files, self.desc_files]
         self.path_handler["save"] = [self.path_save, "Save"]
-        self.path_handler["test"] = [self.path_test, "Tests"]
         self.path_handler["about"] = [self.path_about, "About"]
         self.path_handler["support"] = [self.path_support, "Support"]
         self.path_handler["help"] = [self.path_help, self.desc_help]
@@ -243,6 +242,12 @@ class App(TkBrowser):
         helpnav = ["/help/index", None, "/support", "/info", "/about"]
         mkmenupaths(self.menuhelp, helpnav)
 
+    def open_http(self, path):
+        if path not in HOME_URLS:
+            if not messagebox.askokcancel(parent = self, title = "Visit URL?", 
+                message = "Would you like to open external URL:\n" + path + 
+                " ?"): return
+        webbrowser.open(path)
 
     def open_path(self, loc, withhist = True):
         res = super().open_path(loc, withhist)
@@ -1921,76 +1926,6 @@ class App(TkBrowser):
             self.insert_lb_act("Dialog opcodes", ["save", "dlgops"], "dlgops")
         upd_save()
         return True
-            
-            
-    def path_test(self, path):
-        def display_page():
-            item = None
-            path = self.curr_path
-            if len(path) > 2:
-                item = path[2]
-            self.clear_info()
-            sm = self.gl_state.get("test.info.mode", 0)
-            if item is None:
-                sm = -1
-            self.upd_toolgrp(self.curr_state["gbtns"], sm)
-            if item is None:
-                self.switch_view(0)
-                self.add_info("Select item " + path[1])
-            else:
-                if path[1] == "image":
-                    self.switch_view(1)
-                    self.main_image = tkinter.PhotoImage(file = "img/splash.gif")
-                elif path[1] == "info":
-                    self.switch_view(0)
-                    self.add_info("Information panel for {}\n".format(path))
-                    self.add_info("Local mode {}\n".format(
-                        self.curr_state.get("mode", None)))
-                    self.add_info("Global mode {}\n".format(
-                        self.gl_state.get("test.info.mode", None)))
-                    for i in range(100):
-                        self.add_info("  Item {}\n".format(i))
-            
-        if self.last_path[:1] != ("test",):
-            self.update_gui("Test {}".format(path[1]))
-            self.insert_lb_act("Outline", [])
-            self.insert_lb_act("-", None)
-            for i in range(15):
-                self.insert_lb_act("{} #{}".format(path[1], i), 
-                    path[:2] + (i,), i)
-            # create mode buttons
-            def sw_mode1():
-                print("Mode 1")
-                self.curr_state["mode"] = 1
-                self.curr_state["btn1"].config(state = tkinter.DISABLED)
-                self.curr_state["btn2"].config(state = tkinter.NORMAL)
-                display_page()
-            def sw_mode2():
-                print("Mode 2")
-                self.curr_state["mode"] = 2
-                self.curr_state["btn1"].config(state = tkinter.NORMAL)
-                self.curr_state["btn2"].config(state = tkinter.DISABLED)
-                display_page()
-            self.curr_state["btn1"] = self.add_toolbtn("Mode 1", sw_mode1)
-            self.curr_state["btn2"] = self.add_toolbtn("Mode 2", sw_mode2)
-            # we store buttons in local state
-            self.curr_state["gbtns"] = self.add_toolgrp(None, "test.info.mode",
-                {0: "mode 1", 1: "mode 2", 2: "mode 3"}, display_page)
-
-        # change
-        item = None
-        if len(path) > 2:
-            # index
-            self.select_lb_item(path[2])
-            try:
-                item = path[2]
-            except:
-                pass
-        else:
-            self.select_lb_item(None)
-        # display
-        display_page()
-        return True
 
     def path_about(self, path):
         self.switch_view(0)
diff --git a/engines/petka/testtkgui.py b/engines/petka/testtkgui.py
index 6250041fb..ba8def711 100755
--- a/engines/petka/testtkgui.py
+++ b/engines/petka/testtkgui.py
@@ -28,7 +28,19 @@ class App(TkBrowser):
                 
     def create_widgets(self):
         super().create_widgets()
-        self.open_path("/")
+
+        self.path_handler["test"] = [self.path_test, "Tests"]
+
+        repath = "/"
+        for cmd, arg in self.start_act:
+            if cmd == "open":
+                repath = ""
+                if not self.open_path(arg):
+                    print("DEBUG: stop opening after " + arg)
+                    repath = ""
+                    break
+        if repath:
+            self.open_path(repath)       
 
 
     def create_menu(self):
@@ -41,28 +53,116 @@ class App(TkBrowser):
                 command = self.on_exit,
                 label = "Quit")    
 
+    def path_default(self, path):
+        self.switch_view(0)
+        self.update_gui("Outline")
+        self.clear_info()
+        self.add_info("Open path\n\n" + str(path))
+
+        self.add_info("\n\n")
+        self.add_info("<a href=\"http://example.com/test\">example.com</a>\n")
+        self.add_info("\n\n<b>This is multi-")
+        self.add_info("line\nbold</b>\n")
+        self.add_info("\n\n<u>This is multi-")
+        self.add_info("line\nunderline</u>\n")       
+        self.add_info("\n\n<font color=\"#ff00FF\">This is multi-")
+        self.add_info("line\nunderline</u>\n")
+
+        self.insert_lb_act("Testing", "/test")        
+        return True        
+
+    def path_test(self, path):
+        if len(path) > 1:
+            return self.path_test_item(path)
+        if self.last_path != ("test",):
+            self.update_gui("Test")
+            self.insert_lb_act("Outline", [])
+            self.insert_lb_act("-", None)
+            self.insert_lb_act("Info", "/test/info")
+            self.insert_lb_act("Image", "/test/image")
+            self.switch_view(0)
+            self.clear_info()
+            self.add_info("Select test from outline")            
+        return True
+
+    def path_test_item(self, path):
+        def display_page():
+            item = None
+            path = self.curr_path
+            if len(path) > 2:
+                item = path[2]
+            self.clear_info()
+            sm = self.gl_state.get("test.info.mode", 0)
+            if item is None:
+                sm = -1
+            self.upd_toolgrp(self.curr_state["gbtns"], sm)
+            if item is None:
+                self.switch_view(0)
+                self.add_info("Select item " + path[1])
+            else:
+                if path[1] == "image":
+                    self.switch_view(1)
+                    self.main_image = tkinter.PhotoImage(file = "img/splash.gif")
+                elif path[1] == "info":
+                    self.switch_view(0)
+                    self.add_info("Information panel for {}\n".format(path))
+                    self.add_info("Local mode {}\n".format(
+                        self.curr_state.get("mode", None)))
+                    self.add_info("Global mode {}\n".format(
+                        self.gl_state.get("test.info.mode", None)))
+                    for i in range(100):
+                        self.add_info("  Item {}\n".format(i))
+            
+        if self.last_path[:2] != ("test", path[1]):
+            self.update_gui("Test %s" % path[1])
+            self.insert_lb_act("Testing", "/test")
+            self.insert_lb_act("-", None)
+            for i in range(15):
+                self.insert_lb_act("{} #{}".format(path[1], i), 
+                    path[:2] + (i,), i)
+            # create mode buttons
+            def sw_mode1():
+                print("Mode 1")
+                self.curr_state["mode"] = 1
+                self.curr_state["btn1"].config(state = tkinter.DISABLED)
+                self.curr_state["btn2"].config(state = tkinter.NORMAL)
+                display_page()
+            def sw_mode2():
+                print("Mode 2")
+                self.curr_state["mode"] = 2
+                self.curr_state["btn1"].config(state = tkinter.NORMAL)
+                self.curr_state["btn2"].config(state = tkinter.DISABLED)
+                display_page()
+            self.curr_state["btn1"] = self.add_toolbtn("Mode 1", sw_mode1)
+            self.curr_state["btn1"].config(state = tkinter.DISABLED)
+            self.curr_state["btn2"] = self.add_toolbtn("Mode 2", sw_mode2)
+            # we store buttons in local state
+            self.curr_state["gbtns"] = self.add_toolgrp(None, "test.info.mode",
+                {0: "mode 1", 1: "mode 2", 2: "mode 3"}, display_page)
+
+        # change
+        item = None
+        if len(path) > 2:
+            # index
+            self.select_lb_item(path[2])
+        else:
+            self.select_lb_item(None)
+        # display
+        display_page()
+        return True
+
 def main():
     root = tkinter.Tk()
     app = App(master = root)
     argv = sys.argv[1:]
     while len(argv) > 0:
-        if argv[0] == "-d": # open data
-            app.start_act.append(["load", argv[1]])
-            argv = argv[2:]
-        elif argv[0] == "-s": # open str file
-            app.start_act.append(["str", argv[1]])
-            argv = argv[2:]
-        elif argv[0] == "-sd": # open savex.dat file
-            app.start_act.append(["savedat", argv[1]])
-            argv = argv[2:]
-        elif argv[0] == "-t": # open translation
-            app.start_act.append(["tran", argv[1]])
-            argv = argv[2:]
+        if argv[0] == "--nooutline": # open data
+            app.gui_setup["outline"] = False
+            argv = argv[1:]
         else:
             app.start_act.append(["open", argv[0]])
             argv = argv[1:]
     app.mainloop()
-
     
 if __name__ == "__main__":
     main()
diff --git a/engines/petka/tkguibrowser.py b/engines/petka/tkguibrowser.py
index 0aa376226..95f22d0c6 100644
--- a/engines/petka/tkguibrowser.py
+++ b/engines/petka/tkguibrowser.py
@@ -4,6 +4,7 @@
 # romiq.kh at gmail.com, 2015
 
 import math
+import traceback
 
 import tkinter
 from tkinter import ttk, font, filedialog, messagebox
@@ -56,6 +57,7 @@ def fmt_dec_len(value, add = 0):
 
 # thanx to http://effbot.org/zone/tkinter-text-hyperlink.htm
 class HyperlinkManager:
+
     def __init__(self, text):
         self.text = text
         self.text.tag_config("hyper", foreground = "blue", underline = 1)
@@ -111,8 +113,10 @@ class HyperlinkManager:
                 self.links[tag]()
                 return
 
+
 # thanx http://tkinter.unpythonic.net/wiki/ReadOnlyText
 class ReadOnlyText(tkinter.Text):
+
     def __init__(self, *args, **kwargs):
         tkinter.Text.__init__(self, *args, **kwargs)
         self.redirector = WidgetRedirector(self)
@@ -121,6 +125,7 @@ class ReadOnlyText(tkinter.Text):
         self.delete = \
             self.redirector.register("delete", lambda *args, **kw: "break")
 
+
 class TkBrowser(tkinter.Frame):
     def __init__(self, master):
         tkinter.Frame.__init__(self, master)
@@ -284,29 +289,6 @@ class TkBrowser(tkinter.Frame):
             path = path[:-1]
         return path    
  
-    def open_http(self, path):
-        if path not in HOME_URLS:
-            if not messagebox.askokcancel(parent = self, title = "Visit URL?", 
-                message = "Would you like to open external URL:\n" + path + 
-                " ?"): return
-        webbrowser.open(path)
- 
-    def open_path(self, loc, withhist = True):
-        path = self.parse_path(loc)        
-        if withhist:
-            self.hist.append([path])
-            self.histf = []
-        print("DEBUG: Open", path)
-        self.curr_path = path
-        if len(path) > 0:
-            self.curr_help = path[0]
-        else:
-            self.curr_help = ""
-        if len(path) > 0:
-            if path[0] in self.path_handler:
-                return self.path_handler[path[0]][0](path)
-        return self.path_default(path)
-
     def desc_path(self, loc):
         path = self.parse_path(loc)
         if len(path) > 0:
@@ -504,6 +486,9 @@ class TkBrowser(tkinter.Frame):
     def clear_info(self):
         self.text_view.delete(0.0, tkinter.END)
 
+    def add_text(self, text):
+        self.text_view.insert(tkinter.INSERT, text)
+
     def add_info(self, text):
         mode = 0 # 0 - normal, 1 - tag
         curr_tag = None
@@ -653,10 +638,34 @@ class TkBrowser(tkinter.Frame):
         self.hist = self.hist[-1:]
         self.histf = []
 
+    def open_http(self, path):
+        messagebox.showinfo(parent = self, title = "URL", message = path)
+ 
+    def open_path(self, loc, withhist = True):
+        path = self.parse_path(loc)        
+        if withhist:
+            self.hist.append([path])
+            self.histf = []
+        print("DEBUG: Open", path)
+        self.curr_path = path
+        if len(path) > 0:
+            self.curr_help = path[0]
+        else:
+            self.curr_help = ""
+        try:
+            if len(path) > 0:
+                if path[0] in self.path_handler:
+                    return self.path_handler[path[0]][0](path)
+            return self.path_default(path)
+        except Exception:
+            self.switch_view(0)
+            self.add_text("\n" + "="*20 + "\n" + traceback.format_exc())
+            return True
+
     def path_default(self, path):
         self.switch_view(0)
         self.clear_info()
         self.add_info("Open path\n\n" + str(path))
-        
+        return True
         
 


Commit: 49cc81971b8a8b8824e06c741b9fa67a231e7bf1
    https://github.com/scummvm/scummvm-tools/commit/49cc81971b8a8b8824e06c741b9fa67a231e7bf1
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: linefeed do not break color tags

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/testtkgui.py
    engines/petka/tkguibrowser.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index cb2b466f3..31755529c 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -3,6 +3,8 @@
 2015-06-20 версия 0.4
 ---------------------
 Графический интерфейс выделен в отдельный модуль
+Перевод строк не закрывает теги
+После окончания вывода необходимо вызывать функцию для завершения вывода
 
 2015-01-19 версия 0.3i
 ----------------------
diff --git a/engines/petka/testtkgui.py b/engines/petka/testtkgui.py
index ba8def711..42dc3182e 100755
--- a/engines/petka/testtkgui.py
+++ b/engines/petka/testtkgui.py
@@ -62,11 +62,11 @@ class App(TkBrowser):
         self.add_info("\n\n")
         self.add_info("<a href=\"http://example.com/test\">example.com</a>\n")
         self.add_info("\n\n<b>This is multi-")
-        self.add_info("line\nbold</b>\n")
+        self.add_info("line\nbold</b> text\n")
         self.add_info("\n\n<u>This is multi-")
-        self.add_info("line\nunderline</u>\n")       
+        self.add_info("line\nunderline</u> text\n")       
         self.add_info("\n\n<font color=\"#ff00FF\">This is multi-")
-        self.add_info("line\nunderline</u>\n")
+        self.add_info("line\ncolor</font> text\n")
 
         self.insert_lb_act("Testing", "/test")        
         return True        
diff --git a/engines/petka/tkguibrowser.py b/engines/petka/tkguibrowser.py
index 95f22d0c6..2ed0398c3 100644
--- a/engines/petka/tkguibrowser.py
+++ b/engines/petka/tkguibrowser.py
@@ -127,6 +127,7 @@ class ReadOnlyText(tkinter.Text):
 
 
 class TkBrowser(tkinter.Frame):
+
     def __init__(self, master):
         tkinter.Frame.__init__(self, master)
         self.pack(fill = tkinter.BOTH, expand = 1)        
@@ -140,6 +141,7 @@ class TkBrowser(tkinter.Frame):
         self.last_path = [None]
         self.curr_gui = []
         self.curr_state = {} # local state for location group
+        self.curr_markup = "" # current unparsed markup data (unclosed tags, etc)
         self.curr_lb_acts = None
         self.curr_lb_idx = None
         self.hist = []
@@ -463,8 +465,7 @@ class TkBrowser(tkinter.Frame):
             )
             self.scr_view_x.config(command = self.text_view.xview)
             self.scr_view_y.config(command = self.text_view.yview)
-        else:
-        
+        else:        
             if last == 0:
                 rw = self.text_view.winfo_width()
                 rh = self.text_view.winfo_height()
@@ -487,12 +488,19 @@ class TkBrowser(tkinter.Frame):
         self.text_view.delete(0.0, tkinter.END)
 
     def add_text(self, text):
+        self.end_markup()
         self.text_view.insert(tkinter.INSERT, text)
 
     def add_info(self, text):
+        self.curr_markup += text
+        
+    def end_markup(self):
+        if not self.curr_markup: return
         mode = 0 # 0 - normal, 1 - tag
         curr_tag = None
         curr_text = ""
+        text = self.curr_markup
+        self.curr_markup = ""
         tags = []
         esc = False
         for ch in text:
@@ -506,6 +514,9 @@ class TkBrowser(tkinter.Frame):
                     elif ch == "<":
                         mode = 1
                         curr_tag = ""
+                    elif ch == "\n":
+                        curr_text += ch
+                        pass
                     else:
                         curr_text += ch
             else:
@@ -653,14 +664,15 @@ class TkBrowser(tkinter.Frame):
         else:
             self.curr_help = ""
         try:
-            if len(path) > 0:
-                if path[0] in self.path_handler:
-                    return self.path_handler[path[0]][0](path)
-            return self.path_default(path)
+            if len(path) > 0 and path[0] in self.path_handler:
+                res = self.path_handler[path[0]][0](path)
+            else:
+                res = self.path_default(path)
         except Exception:
             self.switch_view(0)
             self.add_text("\n" + "="*20 + "\n" + traceback.format_exc())
-            return True
+            res = True
+        self.end_markup()
 
     def path_default(self, path):
         self.switch_view(0)


Commit: 9f235c5d8e936a9a390798caa4011b8c572ca068
    https://github.com/scummvm/scummvm-tools/commit/9f235c5d8e936a9a390798caa4011b8c572ca068
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: refactor markup

Changed paths:
    engines/petka/testtkgui.py
    engines/petka/tkguibrowser.py


diff --git a/engines/petka/testtkgui.py b/engines/petka/testtkgui.py
index 42dc3182e..384b5d097 100755
--- a/engines/petka/testtkgui.py
+++ b/engines/petka/testtkgui.py
@@ -68,6 +68,8 @@ class App(TkBrowser):
         self.add_info("\n\n<font color=\"#ff00FF\">This is multi-")
         self.add_info("line\ncolor</font> text\n")
 
+        self.add_info("\n<font bg=\"red\" color=\"white\"> White on red </font>\n")
+
         self.insert_lb_act("Testing", "/test")        
         return True        
 
diff --git a/engines/petka/tkguibrowser.py b/engines/petka/tkguibrowser.py
index 2ed0398c3..99370fa13 100644
--- a/engines/petka/tkguibrowser.py
+++ b/engines/petka/tkguibrowser.py
@@ -113,6 +113,70 @@ class HyperlinkManager:
                 self.links[tag]()
                 return
 
+    def add_markup(self, text, widget, handler):
+        mode = 0 # 0 - normal, 1 - tag
+        curr_tag = None
+        curr_text = ""
+        tags = []
+        esc = False
+        for ch in text:
+            if mode == 0:
+                if esc:
+                    curr_text += ch
+                    esc = False
+                else:
+                    if ch == "\\":
+                        esc = True
+                    elif ch == "<":
+                        mode = 1
+                        curr_tag = ""
+                    elif ch == "\n":
+                        curr_text += ch
+                        pass
+                    else:
+                        curr_text += ch
+            else:
+                if ch == ">":
+                    if len(curr_text) > 0:                    
+                        widget.insert(tkinter.INSERT, curr_text, \
+                            tuple(reversed([x for x in tags for x in x])))
+                    if curr_tag[:7] == "a href=":
+                        ref = curr_tag[7:]
+                        if ref[:1] == "\"":
+                            ref = ref[1:]
+                        if ref[-1:] == "\"":
+                            ref = ref[:-1]
+                        tags.append(self.add(handler(ref)))
+                    elif curr_tag[:11] == "font color=":
+                        ref = curr_tag[11:]
+                        if ref[:1] == "\"":
+                            ref = ref[1:]
+                        if ref[-1:] == "\"":
+                            ref = ref[:-1]
+                        tags.append(self.color(ref))
+                    elif curr_tag[:8] == "font bg=":
+                        ref = curr_tag[8:]
+                        if ref[:1] == "\"":
+                            ref = ref[1:]
+                        if ref[-1:] == "\"":
+                            ref = ref[:-1]
+                        tags.append(self.bg(ref))
+                    elif curr_tag == "b":
+                        tags.append(["bold"])
+                    elif curr_tag == "i":
+                        tags.append(["italic"])
+                    elif curr_tag == "u":
+                        tags.append(["underline"])
+                    elif curr_tag[:1] == "/":
+                        tags = tags[:-1]
+                    curr_text = ""
+                    mode = 0
+                else:
+                    curr_tag += ch
+        if len(curr_text) > 0: 
+            widget.insert(tkinter.INSERT, curr_text, \
+                tuple(reversed([x for x in tags for x in x])))
+    
 
 # thanx http://tkinter.unpythonic.net/wiki/ReadOnlyText
 class ReadOnlyText(tkinter.Text):
@@ -195,7 +259,6 @@ class TkBrowser(tkinter.Frame):
             self.hist.append(np)
             self.open_path(np[0], False)
 
-
     def create_widgets(self):
         ttk.Style().configure("Tool.TButton", width = -1) # minimal width
         ttk.Style().configure("TLabel", padding = self.pad)
@@ -496,76 +559,14 @@ class TkBrowser(tkinter.Frame):
         
     def end_markup(self):
         if not self.curr_markup: return
-        mode = 0 # 0 - normal, 1 - tag
-        curr_tag = None
-        curr_text = ""
-        text = self.curr_markup
+        def make_cb(path):
+            def cb():
+                if path[:5] == "http:" or path[:6] == "https:":
+                    return self.open_http(path)
+                return self.open_path(path)
+            return cb
+        self.text_hl.add_markup(self.curr_markup, self.text_view, make_cb)
         self.curr_markup = ""
-        tags = []
-        esc = False
-        for ch in text:
-            if mode == 0:
-                if esc:
-                    curr_text += ch
-                    esc = False
-                else:
-                    if ch == "\\":
-                        esc = True
-                    elif ch == "<":
-                        mode = 1
-                        curr_tag = ""
-                    elif ch == "\n":
-                        curr_text += ch
-                        pass
-                    else:
-                        curr_text += ch
-            else:
-                if ch == ">":
-                    if len(curr_text) > 0:                    
-                        self.text_view.insert(tkinter.INSERT, curr_text, \
-                            tuple(reversed([x for x in tags for x in x])))
-                    if curr_tag[:7] == "a href=":
-                        ref = curr_tag[7:]
-                        if ref[:1] == "\"":
-                            ref = ref[1:]
-                        if ref[-1:] == "\"":
-                            ref = ref[:-1]
-                        def make_cb(path):
-                            def cb():
-                                if path[:5] == "http:" or path[:6] == "https:":
-                                    return self.open_http(path)
-                                return self.open_path(path)
-                            return cb
-                        tags.append(self.text_hl.add(make_cb(ref)))
-                    elif curr_tag[:11] == "font color=":
-                        ref = curr_tag[11:]
-                        if ref[:1] == "\"":
-                            ref = ref[1:]
-                        if ref[-1:] == "\"":
-                            ref = ref[:-1]
-                        tags.append(self.text_hl.color(ref))
-                    elif curr_tag[:8] == "font bg=":
-                        ref = curr_tag[8:]
-                        if ref[:1] == "\"":
-                            ref = ref[1:]
-                        if ref[-1:] == "\"":
-                            ref = ref[:-1]
-                        tags.append(self.text_hl.bg(ref))
-                    elif curr_tag == "b":
-                        tags.append(["bold"])
-                    elif curr_tag == "i":
-                        tags.append(["italic"])
-                    elif curr_tag == "u":
-                        tags.append(["underline"])
-                    elif curr_tag[:1] == "/":
-                        tags = tags[:-1]
-                    curr_text = ""
-                    mode = 0
-                else:
-                    curr_tag += ch
-        if len(curr_text) > 0: 
-            self.text_view.insert(tkinter.INSERT, curr_text, \
-                tuple(reversed([x for x in tags for x in x])))
         
     def insert_lb_act(self, name, act, key = None):
         if key is not None:


Commit: 66a62d9911f533e0bf5a4ea45242ec975f397db8
    https://github.com/scummvm/scummvm-tools/commit/66a62d9911f533e0bf5a4ea45242ec975f397db8
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: replace html parser with HtmlParser

Changed paths:
    engines/petka/help/changes.txt
    engines/petka/p12explore.py
    engines/petka/testtkgui.py
    engines/petka/tkguibrowser.py


diff --git a/engines/petka/help/changes.txt b/engines/petka/help/changes.txt
index 31755529c..eec0eacb0 100644
--- a/engines/petka/help/changes.txt
+++ b/engines/petka/help/changes.txt
@@ -5,6 +5,7 @@
 Графический интерфейс выделен в отдельный модуль
 Перевод строк не закрывает теги
 После окончания вывода необходимо вызывать функцию для завершения вывода
+Парсинг html переведён на стандартную библиотеку
 
 2015-01-19 версия 0.3i
 ----------------------
diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 70b4b08ed..72bf52d84 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -8,6 +8,7 @@ import urllib.parse
 import tkinter
 from tkinter import filedialog, messagebox
 import traceback
+import webbrowser
 
 from tkguibrowser import TkBrowser, hlesc, cesc, fmt_hl, fmt_hl_len, fmt_arg, \
     fmt_dec, fmt_dec_len
diff --git a/engines/petka/testtkgui.py b/engines/petka/testtkgui.py
index 384b5d097..56a953a71 100755
--- a/engines/petka/testtkgui.py
+++ b/engines/petka/testtkgui.py
@@ -68,7 +68,7 @@ class App(TkBrowser):
         self.add_info("\n\n<font color=\"#ff00FF\">This is multi-")
         self.add_info("line\ncolor</font> text\n")
 
-        self.add_info("\n<font bg=\"red\" color=\"white\"> White on red </font>\n")
+        self.add_info("\n<font bg=\"red\" color=\"white\"> <b>White</b> on <i>red</i> </font>\n")
 
         self.insert_lb_act("Testing", "/test")        
         return True        
diff --git a/engines/petka/tkguibrowser.py b/engines/petka/tkguibrowser.py
index 99370fa13..efa9dfdce 100644
--- a/engines/petka/tkguibrowser.py
+++ b/engines/petka/tkguibrowser.py
@@ -5,6 +5,7 @@
 
 import math
 import traceback
+from html.parser import HTMLParser
 
 import tkinter
 from tkinter import ttk, font, filedialog, messagebox
@@ -54,9 +55,9 @@ def fmt_dec_len(value, add = 0):
         d = int(math.log10(value)) + 1
     d += add
     return d
-
+        
 # thanx to http://effbot.org/zone/tkinter-text-hyperlink.htm
-class HyperlinkManager:
+class HyperlinkManager(HTMLParser):
 
     def __init__(self, text):
         self.text = text
@@ -71,12 +72,17 @@ class HyperlinkManager:
         italic_font.configure(slant = "italic")
         self.text.tag_config("italic", font = italic_font)
         self.text.tag_config("underline", underline = 1)
+        self.parser = HTMLParser()
+        self.parser.handle_starttag = self.handle_starttag
+        self.parser.handle_endtag = self.handle_endtag
+        self.parser.handle_data = self.handle_data
         self.reset()
 
     def reset(self):
     	self.links = {}
     	self.colors = []
     	self.bgs = []
+    	self.colorbgs = []
 
     def add(self, action):
         # add an action to the manager.  returns tags to use in
@@ -101,6 +107,14 @@ class HyperlinkManager:
             self.text.tag_raise("hyper")
         return (tag,)
 
+    def colorbg(self, color, bg):
+        tag = "colorbg-{}".format(color, bg)
+        if tag not in self.colorbgs:
+            self.colorbgs.append(tag)
+            self.text.tag_config(tag, foreground = color, background = bg)
+            self.text.tag_raise("hyper")
+        return (tag,)
+
     def _enter(self, event):
         self.text.config(cursor = "hand2")
 
@@ -113,70 +127,46 @@ class HyperlinkManager:
                 self.links[tag]()
                 return
 
-    def add_markup(self, text, widget, handler):
-        mode = 0 # 0 - normal, 1 - tag
-        curr_tag = None
-        curr_text = ""
-        tags = []
-        esc = False
-        for ch in text:
-            if mode == 0:
-                if esc:
-                    curr_text += ch
-                    esc = False
-                else:
-                    if ch == "\\":
-                        esc = True
-                    elif ch == "<":
-                        mode = 1
-                        curr_tag = ""
-                    elif ch == "\n":
-                        curr_text += ch
-                        pass
-                    else:
-                        curr_text += ch
+    def handle_starttag(self, tag, attrs):
+        tagmap = {"b": "bold", "i": "italic", "u": "underline"}
+        if tag in tagmap:
+            self.parser_tags.append([tagmap[tag]])
+        elif tag == "a":
+            ref = ""
+            for k, v in attrs:
+                if k == "href":
+                    ref = v
+            self.parser_tags.append(self.add(self.parser_handler(ref)))
+        elif tag == "font":
+            color = ""
+            bg = ""
+            for k, v in attrs:
+                if k == "color":
+                    color = v
+                elif k == "bg":
+                    bg = v
+            if color and bg:
+                self.parser_tags.append(self.colorbg(color, bg))
+            elif bg:
+                self.parser_tags.append(self.bg(bg))
             else:
-                if ch == ">":
-                    if len(curr_text) > 0:                    
-                        widget.insert(tkinter.INSERT, curr_text, \
-                            tuple(reversed([x for x in tags for x in x])))
-                    if curr_tag[:7] == "a href=":
-                        ref = curr_tag[7:]
-                        if ref[:1] == "\"":
-                            ref = ref[1:]
-                        if ref[-1:] == "\"":
-                            ref = ref[:-1]
-                        tags.append(self.add(handler(ref)))
-                    elif curr_tag[:11] == "font color=":
-                        ref = curr_tag[11:]
-                        if ref[:1] == "\"":
-                            ref = ref[1:]
-                        if ref[-1:] == "\"":
-                            ref = ref[:-1]
-                        tags.append(self.color(ref))
-                    elif curr_tag[:8] == "font bg=":
-                        ref = curr_tag[8:]
-                        if ref[:1] == "\"":
-                            ref = ref[1:]
-                        if ref[-1:] == "\"":
-                            ref = ref[:-1]
-                        tags.append(self.bg(ref))
-                    elif curr_tag == "b":
-                        tags.append(["bold"])
-                    elif curr_tag == "i":
-                        tags.append(["italic"])
-                    elif curr_tag == "u":
-                        tags.append(["underline"])
-                    elif curr_tag[:1] == "/":
-                        tags = tags[:-1]
-                    curr_text = ""
-                    mode = 0
-                else:
-                    curr_tag += ch
-        if len(curr_text) > 0: 
-            widget.insert(tkinter.INSERT, curr_text, \
-                tuple(reversed([x for x in tags for x in x])))
+                self.parser_tags.append(self.color(color))
     
+    def handle_endtag(self, tag):
+        self.parser_tags = self.parser_tags[:-1]
+
+    def handle_data(self, data):
+        self.parser_widget.insert(tkinter.INSERT, data, \
+            tuple(reversed([x for x in self.parser_tags for x in x])))
+
+    def add_markup(self, text, widget, handler):
+        self.parser_tags = []
+        self.parser_widget = widget
+        self.parser_handler = handler
+        self.parser.reset()
+        self.parser.feed(text)
+        return
+
 
 # thanx http://tkinter.unpythonic.net/wiki/ReadOnlyText
 class ReadOnlyText(tkinter.Text):


Commit: bfb4aa50586d04f5655903812bcf33260da9fa3e
    https://github.com/scummvm/scummvm-tools/commit/bfb4aa50586d04f5655903812bcf33260da9fa3e
Author: Roman Kharin (romiq.kh at gmail.com)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Add new dump function - for automatic testing generated data

Changed paths:
    engines/petka/p12explore.py
    engines/petka/tkguibrowser.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 72bf52d84..3dea0aea6 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -157,6 +157,8 @@ class App(TkBrowser):
                     print("DEBUG: stop opening after " + arg)
                     repath = ""
                     break
+            elif cmd == "dump":
+                self.dump_pages(arg)
         if repath:
             self.open_path(repath)       
 
@@ -2361,6 +2363,144 @@ class App(TkBrowser):
             self.add_info("Error saving \"{}\" \n\n{}".\
                 format(hlesc(fn), hlesc(traceback.format_exc())))
 
+    def dump_pages(self, path):
+        print("Dumping pages")
+        import json
+        import urllib
+        
+        def normalize_link(dest):
+            self.dumpaddr = None
+            def open_path(path):
+                print("  catch " + path)
+                self.dumpaddr = path
+            if callable(dest):
+                # hook self.open_path
+                orig = self.open_path
+                self.open_path = open_path
+                try:
+                    dest()
+                    dest = self.dumpaddr
+                except Exception:
+                    traceback.print_exc()
+                    return
+                finally:
+                    self.open_path = orig
+            elif isinstance(dest, (list, tuple)):
+                dest = "/" + "/".join([str(x) for x in dest])
+            return dest            
+        
+        def normalize_link_html(dest, curr):
+            lnk = normalize_link(dest)
+            if lnk[:1] == "/":
+                lnk = lnk[1:]
+            rel = os.path.relpath(os.path.join(path, lnk), os.path.dirname(curr))
+            if rel == ".":
+                return rel
+            return rel + ".html"
+        
+        def save_data(fn, data, outline):
+            print("  save " + fn)
+            if not os.path.exists(path):
+                os.makedirs(path)
+            # raw
+            with open(fn + ".dat", "wb") as f:
+                f.write(json.dumps(data, indent = 4).encode("UTF-8"))
+            # html
+            head = "<html><head><meta http-equiv=\"Content-Type\" " +\
+                "content=\"text/html; charset=utf-8\">\n</head>"
+            with open(fn + ".html", "w") as f:
+                f.write(head)
+                f.write("<body><table><tr><td width=\"20%\" valign=top>\n")
+
+                f.write("<ul>\n")
+                for name, act in outline:
+                    if act:
+                        f.write("<li><a href=\"%s\">%s</a></li>\n" % (
+                            normalize_link_html(act, fn), name))
+                    else:
+                        f.write("<li>%s</li>\n" % name)
+                f.write("</ul>\n")
+
+                f.write("</td><td valign=top>\n")
+
+                f.write("<pre>")
+                for tp, text, idx in self.text_view.dump("0.0", "end-1c"):
+                    if tp == "tagon":
+                        if text.startswith("hyper-"):
+                            lnk = normalize_link_html(self.text_hl.links[text], fn)
+                            f.write("<a href=\"%s\">" % lnk)
+                        elif text == "bold":
+                            f.write("<b>")
+                        elif text == "italic":
+                            f.write("<i>")
+                        elif text == "underline":
+                            f.write("<u>")
+                        if text.startswith("color-"):
+                            f.write("<font color=\"%s\">" % text[6:])
+                    if tp == "tagoff":
+                        if text.startswith("hyper-"):
+                            f.write("</a>")
+                        elif text == "bold":
+                            f.write("</b>")
+                        elif text == "italic":
+                            f.write("</i>")
+                        elif text == "underline":
+                            f.write("</u>")
+                        if text.startswith("color-"):
+                            f.write("</font>")
+                    elif tp == "text":
+                        f.write(text)
+                f.write("</pre>\n")
+
+                f.write("</td></tr><table>\n")
+                f.write("</body></html>\n")                
+                
+        def save_curr():
+            fn = "/".join([str(x) for x in self.curr_path])
+            fn = os.path.join(path, fn)
+            if not os.path.exists(os.path.dirname(fn)):
+                os.makedirs(os.path.dirname(fn))
+            save_data(fn, self.text_view.dump("0.0", "end-1c"), 
+                self.curr_lb_acts)
+            
+        parsed = []
+        queue = []
+        def addaddr(dest):
+            lnk = normalize_link(dest)
+            if lnk not in parsed + queue:
+                queue.append(lnk)
+                
+        def scan_page(dest):
+            print("  scan " + str(dest))
+            if dest.startswith("/parts/"): return # avoid changing part
+            if dest.startswith("/files/"): return # avoid too big files data
+            #if dest.startswith("/res/"): return 
+            if dest in parsed:
+                return
+            parsed.append(dest)
+            self.open_path(dest)
+            self.update()
+            if self.curr_main == 0:
+                save_curr()
+                # scan text contain
+                for tp, text, idx in self.text_view.dump("0.0", "end-1c"):
+                    if tp == "tagon" and text.startswith("hyper-"):
+                        addaddr(self.text_hl.links[text])
+                        #print(self.text_hl.links[text])
+            else:
+                pass
+            # scan from outline
+            for name, act in self.curr_lb_acts:
+                if act:
+                    addaddr(act)
+        queue.append("/")
+        queue.append("/help")
+        queue.append("/info")
+        while len(queue) > 0:
+            addr = queue[0]
+            queue = queue[1:]
+            scan_page(addr)
+            
 
 def main():
     root = tkinter.Tk()
@@ -2379,6 +2519,9 @@ def main():
         elif argv[0] == "-t": # open translation
             app.start_act.append(["tran", argv[1]])
             argv = argv[2:]
+        elif argv[0] == "-dump": # dump to folder
+            app.start_act.append(["dump", argv[1]])
+            argv = argv[2:]
         else:
             app.start_act.append(["open", argv[0]])
             argv = argv[1:]
diff --git a/engines/petka/tkguibrowser.py b/engines/petka/tkguibrowser.py
index efa9dfdce..2fd4aeb58 100644
--- a/engines/petka/tkguibrowser.py
+++ b/engines/petka/tkguibrowser.py
@@ -108,7 +108,7 @@ class HyperlinkManager(HTMLParser):
         return (tag,)
 
     def colorbg(self, color, bg):
-        tag = "colorbg-{}".format(color, bg)
+        tag = "colorbg-{}|{}".format(color, bg)
         if tag not in self.colorbgs:
             self.colorbgs.append(tag)
             self.text.tag_config(tag, foreground = color, background = bg)
@@ -664,6 +664,7 @@ class TkBrowser(tkinter.Frame):
             self.add_text("\n" + "="*20 + "\n" + traceback.format_exc())
             res = True
         self.end_markup()
+        return res
 
     def path_default(self, path):
         self.switch_view(0)


Commit: 0225608fd336683d8dcbc1a0cf519859cac9a2e5
    https://github.com/scummvm/scummvm-tools/commit/0225608fd336683d8dcbc1a0cf519859cac9a2e5
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
TOOLS: PETKA: Bring to modern python3

Changed paths:
    engines/petka/tkguibrowser.py


diff --git a/engines/petka/tkguibrowser.py b/engines/petka/tkguibrowser.py
index 2fd4aeb58..fe17d6d9a 100644
--- a/engines/petka/tkguibrowser.py
+++ b/engines/petka/tkguibrowser.py
@@ -9,7 +9,7 @@ from html.parser import HTMLParser
 
 import tkinter
 from tkinter import ttk, font, filedialog, messagebox
-from idlelib.WidgetRedirector import WidgetRedirector
+from idlelib.redirector import WidgetRedirector
 
 # Image processing
 try:


Commit: 12ca962b150f2eca76785827c361ede3cbbde29b
    https://github.com/scummvm/scummvm-tools/commit/12ca962b150f2eca76785827c361ede3cbbde29b
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2020-12-06T18:07:15+01:00

Commit Message:
JANITORIAL: Remove trailing spaces

Changed paths:
    engines/petka/p12explore.py
    engines/petka/p12script.py
    engines/petka/petka/__init__.py
    engines/petka/petka/engine.py
    engines/petka/petka/fman.py
    engines/petka/petka/imgbmp.py
    engines/petka/petka/imgflc.py
    engines/petka/petka/imgleg.py
    engines/petka/petka/imgmsk.py
    engines/petka/petka/saves.py
    engines/petka/testtkgui.py
    engines/petka/tkguibrowser.py


diff --git a/engines/petka/p12explore.py b/engines/petka/p12explore.py
index 3dea0aea6..990c6ea47 100755
--- a/engines/petka/p12explore.py
+++ b/engines/petka/p12explore.py
@@ -23,9 +23,9 @@ import petka
 
 APPNAME = "Petka Explorer"
 VERSION = "v0.4 2015-06-20"
-HOME_URLS = ["http://petka-vich.com/petkaexplorer/", 
+HOME_URLS = ["http://petka-vich.com/petkaexplorer/",
             "https://bitbucket.org/romiq/p12simtran"]
-    
+
 def translit(text):
     ru = "абвгдеёзийклмнопрстуфхъыьэАБВГДЕЁЗИЙКЛМНОПРСТУФХЪЫЬЭ"
     en = "abvgdeezijklmnoprstufh'y'eABVGDEEZIJKLMNOPRSTUFH'Y'E"
@@ -64,7 +64,7 @@ class App(TkBrowser):
 
     def __init__(self, master):
         super().__init__(master)
-                
+
     def init_gui(self):
         self.master.title(APPNAME)
         self.clear_data()
@@ -74,7 +74,7 @@ class App(TkBrowser):
         else:
             self.app_path = __file__
         self.app_path = os.path.abspath(os.path.dirname(self.app_path))
-                
+
     def clear_data(self):
         self.sim = None
         self.last_fn = ""
@@ -85,13 +85,13 @@ class App(TkBrowser):
         self.last_savefn = ""
         # translation
         self.tran = None
-           
+
     def create_widgets(self):
         super().create_widgets()
         def desc_def(aname, name, lst = {}):
             def desc(path):
                 if len(path) > 1:
-                    return "{} {}".format(name, lst.get(path[1], 
+                    return "{} {}".format(name, lst.get(path[1],
                         "#{}".format(path[1])))
                 else:
                     return aname
@@ -114,7 +114,7 @@ class App(TkBrowser):
         self.path_handler["casts"] = [self.path_casts,
             desc_def("Casts", "Cast")]
         self.path_handler["opcodes"] = [self.path_opcodes,
-            desc_def("Opcodes", "Opcode", 
+            desc_def("Opcodes", "Opcode",
             {k:v[0] for k, v in petka.engine.OPCODES.items()})]
         self.path_handler["dlgops"] = [self.path_dlgops,
             desc_def("Dialog opcodes", "Dialog opcode",
@@ -160,11 +160,11 @@ class App(TkBrowser):
             elif cmd == "dump":
                 self.dump_pages(arg)
         if repath:
-            self.open_path(repath)       
+            self.open_path(repath)
 
     def create_menu(self):
         super().create_menu()
-        def mkmenupaths(parent, items):                
+        def mkmenupaths(parent, items):
             for n in items:
                 if n is None:
                     parent.add_separator()
@@ -190,17 +190,17 @@ class App(TkBrowser):
         self.menufile.add_separator()
         self.menufile.add_command(
                 command = self.on_exit,
-                label = "Quit")    
+                label = "Quit")
 
         self.menuedit = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menuedit,
                 label = "Edit")
-                
-        editnav = ["/parts", None, "/res", "/objs", "/scenes", "/names", 
-            "/invntr", "/casts", "/msgs", "/dlgs", "/opcodes", "/dlgops", 
+
+        editnav = ["/parts", None, "/res", "/objs", "/scenes", "/names",
+            "/invntr", "/casts", "/msgs", "/dlgs", "/opcodes", "/dlgops",
             "/strs", "/files", "/save"]
         mkmenupaths(self.menuedit, editnav)
-        
+
         self.menunav = tkinter.Menu(self.master, tearoff = 0)
         self.menubar.add_cascade(menu = self.menunav,
                 label = "Navigation")
@@ -247,8 +247,8 @@ class App(TkBrowser):
 
     def open_http(self, path):
         if path not in HOME_URLS:
-            if not messagebox.askokcancel(parent = self, title = "Visit URL?", 
-                message = "Would you like to open external URL:\n" + path + 
+            if not messagebox.askokcancel(parent = self, title = "Visit URL?",
+                message = "Would you like to open external URL:\n" + path +
                 " ?"): return
         webbrowser.open(path)
 
@@ -265,7 +265,7 @@ class App(TkBrowser):
             pass
         self.master.title(capt)
         return res
-        
+
     def on_help(self):
         self.open_path(["help", self.curr_help])
 
@@ -299,13 +299,13 @@ class App(TkBrowser):
             fmt = fmt_hl("/{}/{}".format(pref, rec_id), str(rec_id))
             if full:
                 try:
-                    fmt += " (0x{:X}) - {}".format(rec_id, 
+                    fmt += " (0x{:X}) - {}".format(rec_id,
                         hlesc(self._t(lst_idx[rec_id].name, tt)))
                 except:
                     fmt += " (0x{:X})".format(rec_id)
             return fmt
         return "{} (0x{:X})".format(rec_id, rec_id)
-        
+
     def fmt_hl_res(self, resid, full = True):
         if resid in self.sim.res:
             lnk = fmt_hl("/res/all/{}".format(resid), resid)
@@ -313,10 +313,10 @@ class App(TkBrowser):
                 lnk += " (0x{:X}) - {}".format(resid,
                     hlesc(self.sim.res[resid]))
             return lnk
-        
+
     def fmt_hl_obj(self, obj_id, full = False):
         return self.fmt_hl_rec(self.sim.obj_idx, "objs", obj_id, full, "obj")
-        
+
     def fmt_hl_scene(self, scn_id, full = False):
         return self.fmt_hl_rec(self.sim.scn_idx, "scenes", scn_id, full, "scn")
 
@@ -325,7 +325,7 @@ class App(TkBrowser):
             return self.fmt_hl_rec(self.sim.obj_idx, "objs", rec_id,
                 full, "obj")
         return self.fmt_hl_rec(self.sim.scn_idx, "scenes", rec_id, full, "scn")
-        
+
     def find_path_name(self, key):
         for name_id, name in enumerate(self.sim.namesord):
             if name == key:
@@ -358,7 +358,7 @@ class App(TkBrowser):
         capt = capt or fn
         fid = urllib.parse.quote_plus(fnl)
         return fmt_hl("/files/{}".format(fid), capt)
-        
+
     def path_info_outline(self):
         if self.sim is None and self.strfm is None:
             self.add_info("No data loaded. Open PARTS.INI or SCRIPT.DAT first.")
@@ -366,25 +366,25 @@ class App(TkBrowser):
         if self.sim:
             self.add_info("Current part {} chapter {}\n\n".\
                     format(self.sim.curr_part, self.sim.curr_chap))
-            self.add_info("  Resources:     " + fmt_hl("/res", 
+            self.add_info("  Resources:     " + fmt_hl("/res",
                 len(self.sim.res)) + "\n")
-            self.add_info("  Objects:       " + fmt_hl("/objs", 
+            self.add_info("  Objects:       " + fmt_hl("/objs",
                 len(self.sim.objects)) + "\n")
-            self.add_info("  Scenes:        " + fmt_hl("/scenes", 
+            self.add_info("  Scenes:        " + fmt_hl("/scenes",
                 len(self.sim.scenes)) + "\n")
-            self.add_info("  Names:         " + fmt_hl("/names", 
+            self.add_info("  Names:         " + fmt_hl("/names",
                 len(self.sim.names)) + "\n")
-            self.add_info("  Invntr:        " + fmt_hl("/invntr", 
+            self.add_info("  Invntr:        " + fmt_hl("/invntr",
                 len(self.sim.invntr)) + "\n")
-            self.add_info("  Casts:         " + fmt_hl("/casts", 
+            self.add_info("  Casts:         " + fmt_hl("/casts",
                 len(self.sim.casts)) + "\n")
-            self.add_info("  Messages       " + fmt_hl("/msgs", 
+            self.add_info("  Messages       " + fmt_hl("/msgs",
                 len(self.sim.msgs)) + "\n")
-            self.add_info("  Dialog groups: " + fmt_hl("/dlgs", 
+            self.add_info("  Dialog groups: " + fmt_hl("/dlgs",
                 len(self.sim.dlgs)) + "\n")
-            self.add_info("  Opened stores: " + fmt_hl("strs", 
+            self.add_info("  Opened stores: " + fmt_hl("strs",
                 len(self.strfm.strfd)) + "\n")
-            self.add_info("  Files:         " + fmt_hl("/files", 
+            self.add_info("  Files:         " + fmt_hl("/files",
                 len(self.strfm.strtable)) + "\n")
             scn = hlesc(self.sim.start_scene)
             for scene in self.sim.scenes:
@@ -397,9 +397,9 @@ class App(TkBrowser):
             self.add_info("  " + fmt_hl("/dlgops", "Dialog opcodes") + "\n")
         elif self.strfm:
             self.add_info("Single store mode\n\n")
-            self.add_info("  Opened stores: " + fmt_hl("/strs", 
+            self.add_info("  Opened stores: " + fmt_hl("/strs",
                 len(self.strfm.strfd)) + "\n")
-            self.add_info("  Files:         " + fmt_hl("/files", 
+            self.add_info("  Files:         " + fmt_hl("/files",
                 len(self.strfm.strtable)) + "\n")
         if self.save:
             self.add_info("  " + fmt_hl("/save", "Save") + "\n")
@@ -412,13 +412,13 @@ class App(TkBrowser):
         fmt = fmt_dec(sz, 1)
         fmt = "  " + fmt + ") {}\n"
         for idx, h in enumerate(self.hist[:-1]):
-            self.add_info(fmt.format(idx - len(self.hist) + 1, 
+            self.add_info(fmt.format(idx - len(self.hist) + 1,
                 self.desc_path(h[0])))
-        self.add_info(" {} {}\n".format("=" * fmt_dec_len(sz, 2) + ">", 
+        self.add_info(" {} {}\n".format("=" * fmt_dec_len(sz, 2) + ">",
             self.desc_path(self.curr_path)))
         for idx, h in enumerate(self.histf):
             self.add_info(fmt.format(idx + 1, self.desc_path(h[0])))
-            
+
     def desc_default(self, path):
         desc = ""
         for item in path:
@@ -426,7 +426,7 @@ class App(TkBrowser):
         if not desc:
             desc = "Outline"
         return desc
-        
+
     def path_default(self, path):
         self.switch_view(0)
         self.update_gui("Outline")
@@ -550,7 +550,7 @@ class App(TkBrowser):
                     format(part[0], part[1], hlesc(traceback.format_exc())))
                 return False
         return True
-        
+
     def desc_res(self, path):
         if path == ("res",):
             path = ("res", "all")
@@ -580,8 +580,8 @@ class App(TkBrowser):
             return self.path_default(path)
 
     def path_res_open(self, pref, res_id, mode):
-        self.curr_help = "res_view" # help override 
-        if res_id not in self.sim.res:        
+        self.curr_help = "res_view" # help override
+        if res_id not in self.sim.res:
             self.switch_view(0)
             self.clear_info()
             self.add_info("<b>Resource</b> \"{}\" not found\n".format(res_id))
@@ -608,7 +608,7 @@ class App(TkBrowser):
                         self.add_info("  Mode: {}\n  Size: {}x{}".\
                             format(bmp.image.mode, \
                                 bmp.image.size[0], bmp.image.size[1]))
-                    else:    
+                    else:
                         self.add_info("internal BMP loader\n"\
                             "  Mode: 16-bit\n  Size: {}x{}".\
                             format(bmp.width, bmp.height))
@@ -625,7 +625,7 @@ class App(TkBrowser):
                             format(flc.image.mode, \
                                 flc.image.size[0], flc.image.size[1],
                                 flc.frame_num, flc.image.info["duration"]))
-                    else:    
+                    else:
                         self.add_info("internal FLC loader\n  "\
                             "  Mode:   P\n  Size:   {}x{}\n"\
                             "  Frames: {}\nDelay: {}".\
@@ -644,7 +644,7 @@ class App(TkBrowser):
                         if ru: break
                         for op in act.ops:
                             if res_id == op.op_arg1:
-                                self.add_info("  " + 
+                                self.add_info("  " +
                                     self.fmt_hl_obj_scene(rec.idx, True) + "\n")
                                 ru = True
                                 break
@@ -654,12 +654,12 @@ class App(TkBrowser):
             self.add_info("\n<b>Used by scenes</b>:\n")
             usedby(self.sim.scenes)
             self.add_info("\nFile: {}\n".format(self.fmt_hl_file(fn)))
-            
-                    
+
+
         elif mode[0] == "view":
             self.path_res_view(res_id)
         return True
-                
+
     def path_res_view(self, res_id):
         fn = self.sim.res[res_id]
         try:
@@ -698,7 +698,7 @@ class App(TkBrowser):
     def path_res_status(self):
         self.switch_view(0)
         self.clear_info()
-        self.add_info("<b>Resources</b>: " + fmt_hl("/res", len(self.sim.res)) 
+        self.add_info("<b>Resources</b>: " + fmt_hl("/res", len(self.sim.res))
             + "\nFiletypes:\n")
         fts = {}
         for res in self.sim.res.values():
@@ -713,10 +713,10 @@ class App(TkBrowser):
                 ft, ft, fts[ft]))
         self.select_lb_item(None)
         return True
-    
+
     def on_path_res_info(self):
         self.switch_view(0)
-        
+
     def on_path_res_view(self):
         self.switch_view(1)
 
@@ -728,7 +728,7 @@ class App(TkBrowser):
             for res_id in self.sim.resord:
                     self.insert_lb_act("{} - {}".format(\
                 res_id, self.sim.res[res_id]), ["res", "all", res_id], res_id)
-        # change                
+        # change
         if len(path) > 2:
             return self.path_res_open(path[:3], path[2], path[3:])
         else:
@@ -745,9 +745,9 @@ class App(TkBrowser):
             self.insert_lb_act("-", None)
             for res_id in lst:
                     self.insert_lb_act("{} - {}".format(\
-                res_id, self.sim.res[res_id]), ["res", "flt", path[2], res_id], 
+                res_id, self.sim.res[res_id]), ["res", "flt", path[2], res_id],
                     res_id)
-        # change                
+        # change
         if len(path) > 3:
             return self.path_res_open(path[:4], path[3], path[4:])
         else:
@@ -770,10 +770,10 @@ class App(TkBrowser):
             else:
                 self.update_gui("Scenes ({})".format(len(lst)))
             for rec in lst:
-                self.insert_lb_act("{} - {}".format(rec.idx, 
+                self.insert_lb_act("{} - {}".format(rec.idx,
                     self._t(rec.name, "obj" if isobj else "scn")),\
                     [self.curr_path[0], rec.idx], rec.idx)
-        # change                
+        # change
         rec = None
         if len(path) > 1:
             # index
@@ -805,49 +805,49 @@ class App(TkBrowser):
                 self.add_info("  Name(t):   {}\n".\
                     format(hlesc(self._t(rec.name, "obj" if isobj else "scn"))))
                 if rec.name in self.sim.names:
-                    self.add_info("  " + fmt_hl(self.find_path_name(rec.name), 
+                    self.add_info("  " + fmt_hl(self.find_path_name(rec.name),
                         "Alias") + "(t):  {}\n".format(
                             hlesc(self._t(self.sim.names[rec.name], "obj"))))
                 if rec.name in self.sim.invntr:
-                    self.add_info("  " + fmt_hl(self.find_path_invntr(rec.name), 
+                    self.add_info("  " + fmt_hl(self.find_path_invntr(rec.name),
                         "Invntr") + "(t): {}\n".format(
                             hlesc(self._t(self.sim.invntr[rec.name], "inv"))))
             else:
                 if rec.name in self.sim.names:
-                    self.add_info("  " + fmt_hl(self.find_path_name(rec.name), 
+                    self.add_info("  " + fmt_hl(self.find_path_name(rec.name),
                         "Alias") + ":     {}\n".format(
                             hlesc(self.sim.names[rec.name])))
                 if rec.name in self.sim.invntr:
-                    self.add_info("  " + fmt_hl(self.find_path_invntr(rec.name), 
+                    self.add_info("  " + fmt_hl(self.find_path_invntr(rec.name),
                         "Invntr") + ":    {}\n".format(
                             hlesc(self.sim.invntr[rec.name])))
             if rec.cast:
                 bg = 0
-                r = rec.cast[0] 
+                r = rec.cast[0]
                 g = rec.cast[1]
                 b = rec.cast[2]
-                if (r + g * 2 + b) // 3 < 160: 
+                if (r + g * 2 + b) // 3 < 160:
                     bg = 255
-                self.add_info("  " + fmt_hl(self.find_path_cast(rec.name), 
+                self.add_info("  " + fmt_hl(self.find_path_cast(rec.name),
                     "Cast") + ":      <font bg=\"#{bg:02x}{bg:02x}{bg:02x}\">"
                     "<font color=\"#{r:02x}{g:02x}{b:02x}\">"\
                     "<b> #{r:02x}{g:02x}{b:02x} </b></font></font>\n".\
                     format(bg = bg, r = r, g = g, b = b))
-            
-            # references / backreferences                    
+
+            # references / backreferences
             if isobj:
                 # search where object used
                 self.add_info("\n<b>Refered by scenes</b>:\n")
                 for scn in self.sim.scenes:
                     for ref in scn.refs:
                         if ref[0].idx == rec.idx:
-                            self.add_info("  " + 
+                            self.add_info("  " +
                                 self.fmt_hl_scene(scn.idx, True) + "\n")
                             break
             else:
                 if rec.refs is None:
                     self.add_info("\nNo references\n")
-                else: 
+                else:
                     if len(rec.refs) == 0:
                         self.add_info("\nEmpty references\n")
                     else:
@@ -855,7 +855,7 @@ class App(TkBrowser):
                             format(len(rec.refs)))
                     fmtd = "  " + fmt_dec(len(rec.refs)) + ") "
                     for idx, ref in enumerate(rec.refs):
-                        self.add_info(fmtd.format(idx) + 
+                        self.add_info(fmtd.format(idx) +
                             self.fmt_hl_obj(ref[0].idx))
                         msg = ""
                         for arg in ref[1:]:
@@ -866,7 +866,7 @@ class App(TkBrowser):
                                 msg += "-1"
                             else:
                                 msg += "0x{:X}".format(arg)
-                        self.add_info(msg + self.fmt_cmt(" // " + 
+                        self.add_info(msg + self.fmt_cmt(" // " +
                             self.fmt_hl_obj(ref[0].idx, True)) + "\n")
 
             resused = []
@@ -916,7 +916,7 @@ class App(TkBrowser):
                     if op.op_code == 0x11: # DIALOG
                         if op.op_ref not in dlgused:
                             dlgused.append(op.op_ref)
-                    
+
             if len(resused) > 0:
                 self.add_info("\n<b>Used resources</b>: {}\n".\
                     format(len(resused)))
@@ -928,7 +928,7 @@ class App(TkBrowser):
                 for grp_id in dlgused:
                     self.add_info("  " + self.fmt_hl_dlg(grp_id, True)+ "\n")
 
-            # messages used by this object            
+            # messages used by this object
             if isobj:
                 wasmsg = False
                 for msg in self.sim.msgs:
@@ -958,16 +958,16 @@ class App(TkBrowser):
                 if k not in oplst: continue
                 self.add_info("\n<b>Used in " + self.fmt_opcode(k) + "</b>:\n")
                 for oid, htp, hid in oplst[k]:
-                    self.add_info("  " + self.fmt_hl_obj_scene(oid) + 
-                      " on " + self.fmt_opcode(htp) + 
-                      " #{}".format(hid) + self.fmt_cmt(" // " + 
+                    self.add_info("  " + self.fmt_hl_obj_scene(oid) +
+                      " on " + self.fmt_opcode(htp) +
+                      " #{}".format(hid) + self.fmt_cmt(" // " +
                       self.fmt_hl_obj_scene(oid, True)) + "\n")
 
             # perspective
             if not isobj and rec.persp:
                 self.add_info("\n<b>Perspective</b>:\n  {}, {}, {}, {}, {}\n".
                     format(*rec.persp))
-                
+
             # enter areas
             if not isobj and rec.entareas:
                 self.add_info("\n<b>Enter areas</b>: {}\n".format(
@@ -978,8 +978,8 @@ class App(TkBrowser):
                     self.add_info("    <i>on</i>: {}\n".format(
                         self.fmt_hl_obj(oo.idx, True)))
         return True
-                
-    def path_std_items(self, path, level, guiname, guiitem, tt, lst, lst_idx, 
+
+    def path_std_items(self, path, level, guiname, guiitem, tt, lst, lst_idx,
             lbmode, cb):
         self.switch_view(0)
         if self.last_path[:level] != path[:level]:
@@ -1008,7 +1008,7 @@ class App(TkBrowser):
             # info
             cb(name)
         return True
-        
+
     def path_names(self, path):
         if self.sim is None:
             return self.path_default([])
@@ -1026,9 +1026,9 @@ class App(TkBrowser):
             for obj in self.sim.objects:
                 if obj.name == name:
                     self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
-        return self.path_std_items(path, 1, "Names", "name", "obj", 
+        return self.path_std_items(path, 1, "Names", "name", "obj",
             self.sim.names, self.sim.namesord, 0, info)
-                            
+
     def path_invntr(self, path):
         if self.sim is None:
             return self.path_default([])
@@ -1046,7 +1046,7 @@ class App(TkBrowser):
             for obj in self.sim.objects:
                 if obj.name == name:
                     self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
-        return self.path_std_items(path, 1, "Invntr", "invntr", "obj", 
+        return self.path_std_items(path, 1, "Invntr", "invntr", "obj",
             self.sim.invntr, self.sim.invntrord, 0, info)
 
     def path_casts(self, path):
@@ -1075,7 +1075,7 @@ class App(TkBrowser):
             for idx, obj in enumerate(self.sim.objects):
                 if obj.name == name:
                     self.add_info("  " + self.fmt_hl_obj(obj.idx, True) + "\n")
-        return self.path_std_items(path, 1, "Cast", "cast", "obj", 
+        return self.path_std_items(path, 1, "Cast", "cast", "obj",
             self.sim.casts, self.sim.castsord, 0, info)
 
     def path_msgs(self, path):
@@ -1118,7 +1118,7 @@ class App(TkBrowser):
                 lst.sort()
                 fmtlen = fmt_dec_len(len(lst))
                 for _, wav, idx, capt in lst:
-                    self.add_info("  " + fmt_hl_len("/msgs/{}".format(idx), 
+                    self.add_info("  " + fmt_hl_len("/msgs/{}".format(idx),
                         "{}".format(idx), fmtlen))
                     self.add_info(" - {} - {}\n".format(
                         self.fmt_hl_file("speech{}/{}".format(
@@ -1129,7 +1129,7 @@ class App(TkBrowser):
                 self.add_info("  wav:    {}\n".
                         format(self.fmt_hl_file("speech{}/{}".format(
                             self.sim.curr_part, msg.msg_wav), msg.msg_wav)))
-                self.add_info("  object: " + self.fmt_hl_obj(msg.obj.idx, 
+                self.add_info("  object: " + self.fmt_hl_obj(msg.obj.idx,
                     True) + "\n")
                 self.add_info("  arg2:   {a} (0x{a:X})\n".format(
                     a = msg.msg_arg2))
@@ -1146,9 +1146,9 @@ class App(TkBrowser):
                             for op in dlg.ops:
                                 if not op.msg: continue
                                 if op.msg.idx == msg.idx and op.opcode == 7:
-                                    self.add_info("  " + 
+                                    self.add_info("  " +
                                         self.fmt_hl_dlg(grp.idx, True) + "\n")
-        
+
         if self.last_path[:1] != ("msgs",):
             self.update_gui("Messages ({})".format(len(self.sim.msgs)))
             for idx, msg in enumerate(self.sim.msgs):
@@ -1159,7 +1159,7 @@ class App(TkBrowser):
                     capt = capt[:40] + "|"
                 self.insert_lb_act("{} - {}".format(msg.idx, capt),
                     ["msgs", idx], idx)
-            self.curr_state["btnsort"] = self.add_toolgrp("Sort by", 
+            self.curr_state["btnsort"] = self.add_toolgrp("Sort by",
                 "msgs.sort", {0: "wav", 1: "order", 2: "text"}, upd_msgs)
         # change
         upd_msgs()
@@ -1205,14 +1205,14 @@ class App(TkBrowser):
             fmtga = "  " + fmt_dec(len(grp.acts)) + \
                 ") <u>on {} {} 0x{:X} 0x{:X}</u>, dlgs: {}{}\n"
             for idx, act in enumerate(grp.acts):
-                self.add_info(fmtga.format(idx, self.fmt_opcode(act.opcode), 
+                self.add_info(fmtga.format(idx, self.fmt_opcode(act.opcode),
                         self.fmt_hl_obj(act.ref), act.arg1, act.arg2, \
-                        len(act.dlgs), self.fmt_cmt(" // " + 
+                        len(act.dlgs), self.fmt_cmt(" // " +
                             self.fmt_hl_obj(act.ref, True))))
                 fmtad = "    " + fmt_dec(len(act.dlgs)) + \
                     ") <i>0x{:X} 0x{:X}</i>, ops: {}\n"
                 for didx, dlg in enumerate(act.dlgs):
-                    self.add_info(fmtad.format(didx, dlg.arg1, dlg.arg2, 
+                    self.add_info(fmtad.format(didx, dlg.arg1, dlg.arg2,
                         len(dlg.ops)))
                     # scan for used adreses
                     usedadr = []
@@ -1287,9 +1287,9 @@ class App(TkBrowser):
                                 opref = self.fmt_hl_msg(op.ref)
                                 objref = self.fmt_hl_obj(op.msg.obj.idx)
                                 cmt = self.fmt_cmt(" // obj={}, msg={}".\
-                                    format(objref, 
+                                    format(objref,
                                         self.fmt_hl_msg(op.ref, True)))
-                        
+
                         oparg = " 0x{:X} ".format(op.arg)
                         if (op.opcode == 0x1 or op.opcode == 0x6) and \
                                 op.arg == 0 and op.ref == 0:
@@ -1306,7 +1306,7 @@ class App(TkBrowser):
                         if not hdr:
                             self.add_info(hl)
                             hdr = True
-                        self.add_info("  linked " + 
+                        self.add_info("  linked " +
                             self.fmt_hl_obj_scene(rec.idx, True) + "\n")
                         continue
                     ru = False
@@ -1314,11 +1314,11 @@ class App(TkBrowser):
                         if ru: break
                         for op in act.ops:
                             if op.op_code == 0x11 and \
-                                    op.op_ref == grp.idx: # DIALOG 
+                                    op.op_ref == grp.idx: # DIALOG
                                 if not hdr:
                                     self.add_info(hl)
                                     hdr = True
-                                self.add_info("  " + 
+                                self.add_info("  " +
                                     self.fmt_hl_obj_scene(rec.idx, True) + "\n")
                                 ru = True
                                 break
@@ -1327,7 +1327,7 @@ class App(TkBrowser):
             usedby(self.sim.objects, "\n<b>Used by objects</b>:\n")
             usedby(self.sim.scenes, "\n<b>Used by scenes</b>:\n")
         return True
-        
+
     def path_opcodes(self, path):
         if self.sim is None:
             return self.path_default([])
@@ -1344,7 +1344,7 @@ class App(TkBrowser):
                     if act.act_op not in keys:
                         keys.append(act.act_op)
                     for op in act.ops:
-                        opstat[op.op_code] = opstat.get(op.op_code, 
+                        opstat[op.op_code] = opstat.get(op.op_code,
                             0) + 1
                         if op.op_code not in keys:
                             keys.append(op.op_code)
@@ -1360,7 +1360,7 @@ class App(TkBrowser):
             keys, opstat, acstat, dastat = keyslist()
             self.update_gui("Opcodes ({})".format(len(keys)))
             for key in keys:
-                self.insert_lb_act("{} - {}".format(key, self.fmt_opcode(key, 
+                self.insert_lb_act("{} - {}".format(key, self.fmt_opcode(key,
                     True)), ["opcodes", key], key)
         # change
         opcode = None
@@ -1390,7 +1390,7 @@ class App(TkBrowser):
                 mcnt = len(msg)
                 msg += " - {}".format(fmt_hl("/opcodes/{}".format(
                     key), opname))
-                mcnt += len(self.fmt_opcode(key, True))    
+                mcnt += len(self.fmt_opcode(key, True))
                 while mcnt < 23:
                     msg += " "
                     mcnt += 1
@@ -1420,43 +1420,43 @@ class App(TkBrowser):
             # display
             if len(ops) == 0:
                 self.add_info("<i>Not used in scripts</i>\n\n")
-            else:            
+            else:
                 self.add_info("<i>Used in scripts</i>: {}\n".format(len(ops)))
                 fmtops = "  " + fmt_dec(len(ops)) + \
                     ") obj={}, act={}, op={} {}\n"
                 for idx, (obj_idx, aidx, oidx) in enumerate(ops):
                     self.add_info(fmtops.format(
                         idx, self.fmt_hl_obj_scene(obj_idx, False), aidx, oidx,
-                        self.fmt_cmt("// " + self.fmt_hl_obj_scene(obj_idx, 
+                        self.fmt_cmt("// " + self.fmt_hl_obj_scene(obj_idx,
                         True))))
-                self.add_info("\n")  
+                self.add_info("\n")
 
             if len(acts) == 0:
                 self.add_info("<i>Not used in handlers</i>\n\n")
-            else:            
+            else:
                 self.add_info("<i>Used in handlers</i>: {}\n".format(len(acts)))
                 fmtacts = "  " + fmt_dec(len(acts)) + \
                     ") obj={}, act={} {}\n"
                 for idx, (obj_idx, aidx) in enumerate(acts):
                     self.add_info(fmtacts.format(
-                        idx, self.fmt_hl_obj_scene(obj_idx, False), aidx, 
-                        self.fmt_cmt("// " + self.fmt_hl_obj_scene(obj_idx, 
+                        idx, self.fmt_hl_obj_scene(obj_idx, False), aidx,
+                        self.fmt_cmt("// " + self.fmt_hl_obj_scene(obj_idx,
                         True))))
-                self.add_info("\n")  
+                self.add_info("\n")
 
             if len(dacts) == 0:
                 self.add_info("<i>Not used in dialog handlers</i>\n\n")
-            else:            
+            else:
                 self.add_info("<i>Used in dialog handlers</i>: {}\n".format(
                     len(dacts)))
                 fmtdacts = "  " + fmt_dec(len(dacts)) + \
                     ") obj={}, group=<a href=\"/dlgs/{}\">{}</a>, act={} {}\n"
                 for idx, (obj_idx, gidx, aidx) in enumerate(dacts):
                     self.add_info(fmtdacts.format(
-                        idx, self.fmt_hl_obj_scene(obj_idx, False), gidx, gidx, 
+                        idx, self.fmt_hl_obj_scene(obj_idx, False), gidx, gidx,
                         aidx, self.fmt_cmt("// " + self.fmt_hl_obj_scene(
                         obj_idx, True))))
-                self.add_info("\n")  
+                self.add_info("\n")
         return True
 
     def path_dlgops(self, path):
@@ -1481,7 +1481,7 @@ class App(TkBrowser):
             keys, dlstat = keyslist()
             self.update_gui("Dialog opcodes ({})".format(len(keys)))
             for key in keys:
-                self.insert_lb_act("{} - {}".format(key, self.fmt_dlgop(key, 
+                self.insert_lb_act("{} - {}".format(key, self.fmt_dlgop(key,
                     True)), ["dlgops", key], key)
         # change
         opcode = None
@@ -1510,7 +1510,7 @@ class App(TkBrowser):
                 mcnt = len(msg)
                 msg += " - {}".format(fmt_hl("/dlgops/{}".format(
                     key), opname))
-                mcnt += len(self.fmt_dlgop(key, True))    
+                mcnt += len(self.fmt_dlgop(key, True))
                 while mcnt < 20:
                     msg += " "
                     mcnt += 1
@@ -1530,19 +1530,19 @@ class App(TkBrowser):
             # display
             if len(dls) == 0:
                 self.add_info("<i>Not used in dialogs</i>\n\n")
-            else:            
+            else:
                 self.add_info("<i>Used in dialogs</i>: {}\n".format(len(dls)))
                 fmtdls = "  " + fmt_dec(len(dls)) + \
                     ") obj={}, group=<a href=\"/dlgs/{}\">{}" + \
                         "</a>, act={}, dlg={}, op={} {}\n"
                 for idx, (obj_idx, gidx, aidx, didx, oidx) in enumerate(dls):
                     self.add_info(fmtdls.format(
-                        idx, self.fmt_hl_obj_scene(obj_idx, False), gidx, gidx, 
-                        aidx, didx, oidx, self.fmt_cmt("// " + 
+                        idx, self.fmt_hl_obj_scene(obj_idx, False), gidx, gidx,
+                        aidx, didx, oidx, self.fmt_cmt("// " +
                         self.fmt_hl_obj_scene(obj_idx, True))))
-                self.add_info("\n")  
+                self.add_info("\n")
         return True
-       
+
     def path_stores(self, path):
         if self.strfm is None:
             return self.path_default([])
@@ -1585,7 +1585,7 @@ class App(TkBrowser):
                 if len(strlst):
                     flnk = self.fmt_hl_file(strlst[0][0], flnk)
                 self.add_info("  Files: {}, Tag: {}\n\n".format(flnk, tag))
-                
+
                 lst = []
                 for idx, (fname, _, _, _) in enumerate(strlst):
                     if sm == 0:
@@ -1596,9 +1596,9 @@ class App(TkBrowser):
                 lst.sort()
                 fmt = "  " + fmt_dec(len(lst)) + ") {}\n"
                 for _, idx, fname in lst:
-                    self.add_info(fmt.format(idx + 1, 
+                    self.add_info(fmt.format(idx + 1,
                         self.fmt_hl_file(fname)))
-            
+
         self.switch_view(0)
         keys = None
         if self.last_path[:1] != ("strs",):
@@ -1618,7 +1618,7 @@ class App(TkBrowser):
                     return
                 # extract to folder
                 sdir = filedialog.askdirectory(parent = self,
-                    title = "Select folder for extract", 
+                    title = "Select folder for extract",
                     initialdir = self.strfm.root, mustexist = True)
                 if not sdir: return
                 # extract store to sdir
@@ -1644,7 +1644,7 @@ class App(TkBrowser):
                             format(hlesc(fname), hlesc(traceback.format_exc())))
                         return
             self.curr_state["btnext"] = self.add_toolbtn("Extract STR", ext_str)
-            self.curr_state["btnsort"] = self.add_toolgrp("Sort by", 
+            self.curr_state["btnsort"] = self.add_toolgrp("Sort by",
                 "strs.sortfiles", {0: "order", 1: "filename"}, upd_strs)
         upd_strs()
         return True
@@ -1698,7 +1698,7 @@ class App(TkBrowser):
                         format(hlesc(fnl)))
                     return
                 self.add_info("<b>File</b>: {}\n\n".format(fnl))
-                    
+
                 # search in loaded stores
                 if fnl in self.strfm.strtable:
                     # in store
@@ -1720,7 +1720,7 @@ class App(TkBrowser):
                                 self.add_info("\n<b>Used in resources</b>:\n")
                                 wascapt = True
                             self.add_info("  {} - <a href=\"/res/all/{}\">"\
-                                "{}</a>\n".format(resid, resid, 
+                                "{}</a>\n".format(resid, resid,
                                 hlesc(self.sim.res[resid])))
                     wavpref = "speech{}/".format(self.sim.curr_part)
                     if fnl[-4:] == ".wav" and fnl.startswith(wavpref):
@@ -1780,9 +1780,9 @@ class App(TkBrowser):
                 if sa:
                     self.add_info("\n<b>See also</b>:\n")
                     for p in sa:
-                        self.add_info("  " + fmt_hl(p, self.desc_path(p)) 
+                        self.add_info("  " + fmt_hl(p, self.desc_path(p))
                             + "\n")
-                        
+
                 if fnl[-4:] in [".leg", ".off"]:
                     legf = petka.LEGLoader()
                     legf.load_data(self.strfm.read_file_stream(fnl))
@@ -1791,7 +1791,7 @@ class App(TkBrowser):
                     fmt = "  " + fmt_dec(len(legf.coords)) + ") {}, {}\n"
                     for idx, (x, y) in enumerate(legf.coords):
                         self.add_info(fmt.format(idx + 1, x, y))
-                        
+
                 if fnl[-4:] == ".msk":
                     mskf = petka.MSKLoader()
                     mskf.load_data(self.strfm.read_file_stream(fnl))
@@ -1806,7 +1806,7 @@ class App(TkBrowser):
                         fmtr = "    " + fmt_dec(len(rs)) + ") {}, {} - {}, {}\n"
                         for idxr, r in enumerate(rs):
                             self.add_info(fmtr.format(idxr + 1, *r))
-                        
+
                 if fnl[-4:] == ".flc":
                     flcf = petka.FLCLoader()
                     flcf.load_info(self.strfm.read_file_stream(fnl))
@@ -1818,13 +1818,13 @@ class App(TkBrowser):
                             format(flcf.image.mode, \
                                 flcf.image.size[0], flcf.image.size[1],
                                 flcf.frame_num, flcf.image.info["duration"]))
-                    else:    
+                    else:
                         self.add_info("\n<b>FLC data</b> (internal)\n")
                         self.add_info("  Mode:   P\n  Size:   {}x{}\n"\
                             "  Frames: {}\nDelay: {}".\
                             format(flcf.width, flcf.height, \
                                 flcf.frame_num, flcf.delay))
-                        
+
         self.switch_view(0)
         keys = None
         if self.last_path[:1] != ("files",):
@@ -1834,7 +1834,7 @@ class App(TkBrowser):
                 fnl = fn.lower().replace("\\", "/")
                 fnl = urllib.parse.quote_plus(fnl)
                 self.insert_lb_act(fn, ["files", fnl], fnl)
-            self.curr_state["btnsort"] = self.add_toolgrp("Sort by", 
+            self.curr_state["btnsort"] = self.add_toolgrp("Sort by",
                 "files.sort", {0: "order", 1: "filename"}, upd_files)
         upd_files()
         return True
@@ -1842,7 +1842,7 @@ class App(TkBrowser):
     def path_save(self, path):
         if self.sim is None:
             return self.path_default([])
-            
+
         def upd_save():
             path = self.curr_path
             fid = None
@@ -1850,7 +1850,7 @@ class App(TkBrowser):
             if self.save is None:
                 self.add_info("<b>Saved state</b>: not loaded\n\n")
                 return
-                
+
             if path == ("save",):
                 path = ("save", "info")
             if path[1] == "shot":
@@ -1870,24 +1870,24 @@ class App(TkBrowser):
                 self.add_info("  cursor: {} - {}\n".format(self.save.cursor,
                     petka.ACTIONS.get(self.save.cursor, ["ACT{:2X}".format(
                         self.save.cursor), 0])[0]))
-                self.add_info("    " + self.fmt_hl_res(self.save.cursor_res, 
+                self.add_info("    " + self.fmt_hl_res(self.save.cursor_res,
                     True) + "\n")
                 if self.save.cursor_obj == 0xffffffff:
                     self.add_info("  object: <i>none</i>\n")
                 else:
                     self.add_info("  object: " + self.fmt_hl_obj(
                         self.save.cursor_obj, True) + "\n")
-                self.add_info("  char 1: {}, {} ".format(self.save.char1[0], 
+                self.add_info("  char 1: {}, {} ".format(self.save.char1[0],
                     self.save.char1[1]) + self.fmt_hl_res(
                     self.save.char1[2], True) + "\n")
-                self.add_info("  char 2: {}, {} ".format(self.save.char2[0], 
+                self.add_info("  char 2: {}, {} ".format(self.save.char2[0],
                     self.save.char2[1]) + self.fmt_hl_res(
                     self.save.char2[2], True) + "\n")
-                
+
                 self.add_info("  invntr: {}\n".format(len(self.save.invntr)))
                 fmt = "    " + fmt_dec(len(self.save.invntr)) + ") "
                 for idx, inv in enumerate(self.save.invntr):
-                        self.add_info(fmt.format(idx + 1) + 
+                        self.add_info(fmt.format(idx + 1) +
                             self.fmt_hl_obj_scene(inv, True) + "\n")
 
                 self.add_info("\n  objects: {}\n".format(len(
@@ -1895,7 +1895,7 @@ class App(TkBrowser):
                 fmt = "  " + fmt_dec(len(self.save.objects)) + ") \"{}\" {}\n"
 
                 for idx, obj in enumerate(self.save.objects):
-                    self.add_info(fmt.format(idx + 1, obj["name"], 
+                    self.add_info(fmt.format(idx + 1, obj["name"],
                         obj["alias"]))
                     fndobj = None
                     for sobj in self.sim.objects + self.sim.scenes:
@@ -1903,12 +1903,12 @@ class App(TkBrowser):
                             fndobj = sobj
                             break
                     if fndobj:
-                        self.add_info("    " + 
+                        self.add_info("    " +
                             self.fmt_hl_obj_scene(fndobj.idx, True) + "\n")
                     self.add_info("    {}, {}, {}, {}, {}, {}, {}, {}, {}\n".
                         format(*obj["recs"]))
                     if obj["res"] in self.sim.res:
-                        self.add_info("    " + self.fmt_hl_res(obj["res"], 
+                        self.add_info("    " + self.fmt_hl_res(obj["res"],
                             True) + "\n")
             elif path[1] == "dlgops":
                 self.clear_info()
@@ -1920,7 +1920,7 @@ class App(TkBrowser):
                     self.add_info(fmt.format(idx + 1, self.fmt_dlgop(code), arg, ref))
             else:
                 self.select_lb_item("info")
-                        
+
         if self.last_path[:1] != ("save",):
             # calc statistics
             self.update_gui("Save")
@@ -1964,7 +1964,7 @@ class App(TkBrowser):
                 self.add_info("<i>no tranlation file</i>\n")
             else:
                 self.add_info(hlesc(self.tran_fn) + "\n")
-                
+
         self.add_info("<b>Engine</b>:       ")
         if not self.sim:
             self.add_info("<i>not initialized</i>\n")
@@ -1997,14 +1997,14 @@ class App(TkBrowser):
             self.path_info_outline()
 
         return True
-            
+
     def desc_help(self, path):
         if path == ("help",):
             path = ("help", "index")
         if path == ("help", "index"):
             return "Index"
         return "Help for " + self.desc_path(path[1])
-            
+
     def path_help(self, path):
         self.switch_view(0)
         if path == ("help",):
@@ -2019,16 +2019,16 @@ class App(TkBrowser):
                     item = item.decode("UTF-8").strip()
                     if not item: continue
                     try:
-                        hf = open(os.path.join(self.app_path, 
+                        hf = open(os.path.join(self.app_path,
                             "help", item + ".txt"), "rb")
                         try:
                             for line in hf.readlines():
-                                line = line.decode("UTF-8").strip()                    
+                                line = line.decode("UTF-8").strip()
                                 break
                         finally:
                             hf.close()
                     except:
-                        line = item                    
+                        line = item
                     self.insert_lb_act(line, ["help", item], item)
             finally:
                 f.close()
@@ -2046,10 +2046,10 @@ class App(TkBrowser):
                 hf = open(hfn, "rb")
                 try:
                     for idx, line in enumerate(hf.readlines()):
-                        line = line.decode("UTF-8").rstrip()                    
+                        line = line.decode("UTF-8").rstrip()
                         if idx == 0:
                             self.add_info("<b>" + line + "</b>\n")
-                        else:                                        
+                        else:
                             self.add_info(line + "\n")
                 finally:
                     hf.close()
@@ -2060,7 +2060,7 @@ class App(TkBrowser):
             self.select_lb_item(None)
 
         return True
-        
+
     def path_info(self, path):
         self.switch_view(0)
         if self.last_path[:1] != ("info",):
@@ -2088,7 +2088,7 @@ class App(TkBrowser):
                 k.sort()
                 for key in k:
                     self.add_info("  {} (0x{:X}) - {}\n".format(key, key,
-                        petka.OPCODES[key][0])) 
+                        petka.OPCODES[key][0]))
             elif name == "dlgops":
                 self.add_info("<b>Dialog opcodes<b>\n\n")
                 k = list(petka.DLGOPS.keys())
@@ -2103,9 +2103,9 @@ class App(TkBrowser):
                 for key in k:
                     self.add_info("  {} (0x{:X}) - {}\n".format(key, key,
                         petka.ACTIONS[key][0]))
-                    self.add_info("    " + 
+                    self.add_info("    " +
                         self.fmt_hl_res(petka.ACTIONS[key][1]) + "\n")
-            else: 
+            else:
                 self.add_info("Unknown data type \"{}\"\n".format(hlesc(name)))
 
         return True
@@ -2113,7 +2113,7 @@ class App(TkBrowser):
     def on_open_data(self):
         ft = [\
             ('all files', '.*')]
-        fn = filedialog.askopenfilename(parent = self, 
+        fn = filedialog.askopenfilename(parent = self,
             title = "Open PARTS.INI or SCRIPT.DAT",
             filetypes = ft,
             initialdir = os.path.abspath(os.curdir))
@@ -2123,7 +2123,7 @@ class App(TkBrowser):
         if self.open_data_from(os.path.dirname(fn)):
             self.open_path("")
             self.clear_hist()
-        
+
     def open_data_from(self, folder):
         self.last_fn = folder
         self.clear_data()
@@ -2146,7 +2146,7 @@ class App(TkBrowser):
         ft = [\
             ('STR files', '.STR'),
             ('all files', '.*')]
-        fn = filedialog.askopenfilename(parent = self, 
+        fn = filedialog.askopenfilename(parent = self,
             title = "Open STR file",
             filetypes = ft,
             initialdir = os.path.abspath(os.curdir))
@@ -2176,7 +2176,7 @@ class App(TkBrowser):
         ft = [\
             ('Save files (*.dat)', '.DAT'),
             ('all files', '.*')]
-        fn = filedialog.askopenfilename(parent = self, 
+        fn = filedialog.askopenfilename(parent = self,
             title = "Open SAVEx.DAT file",
             filetypes = ft,
             initialdir = os.path.abspath(os.curdir))
@@ -2190,21 +2190,21 @@ class App(TkBrowser):
             self.switch_view(0)
             self.update_gui("")
             self.clear_info()
-            self.add_info("Open data before loading saves")  
+            self.add_info("Open data before loading saves")
             return
         self.save = None
         try:
             self.last_savefn = fn
             self.save = petka.SaveLoader("cp1251")
             with open(fn, "rb") as f:
-                self.save.load_data(f, self.sim.curr_part, 
+                self.save.load_data(f, self.sim.curr_part,
                     len(self.sim.objects) + len(self.sim.scenes))
                 if self.save.part != self.sim.curr_part:
                     # load
                     print("DEBUG: change part {}".format(self.save.part))
                     self.sim.open_part(self.save.part, 0)
                     f.seek(0)
-                    self.save.load_data(f, self.sim.curr_part, 
+                    self.save.load_data(f, self.sim.curr_part,
                         len(self.sim.objects) + len(self.sim.scenes))
             return True
         except:
@@ -2215,18 +2215,18 @@ class App(TkBrowser):
             self.clear_info()
             self.add_info("Error opening \"{}\" \n\n{}".\
                 format(hlesc(fn), hlesc(traceback.format_exc())))
-            
+
 
     def on_tran_save(self):
         self.on_tran_save_real()
 
     def on_tran_save_tlt(self):
         self.on_tran_save_real(True)
-        
+
     def on_tran_save_real(self, tlt = False):
         # save dialog
         fn = filedialog.asksaveasfilename(parent = self,
-            title = "Save translate template (.pot)" + 
+            title = "Save translate template (.pot)" +
                 " (transliterate)" if tlt else "",
             filetypes = [('PO Template', ".pot"), ('all files', '.*')],
             initialdir = os.path.abspath(os.curdir))
@@ -2248,19 +2248,19 @@ class App(TkBrowser):
                     ts = translit(text)
                 entry = polib.POEntry(
                     msgid = text, msgstr = ts, comment = cmt)
-                po.append(entry)                
+                po.append(entry)
             for rec in self.sim.objects:
                 saveitem(rec.name, "obj_{}".format(rec.idx))
             for rec in self.sim.scenes:
                 saveitem(rec.name, "scn_{}".format(rec.idx))
             for idx, name in enumerate(self.sim.namesord):
-                saveitem(self.sim.names[name], 
+                saveitem(self.sim.names[name],
                     "name_{}, {}".format(idx, name))
             for idx, name in enumerate(self.sim.invntrord):
                 saveitem(self.sim.invntr[name],
                     "inv_{}, {}".format(idx, name))
             for idx, msg in enumerate(self.sim.msgs):
-                saveitem(msg.name, 
+                saveitem(msg.name,
                     "msg_{}, {} - {}".format(idx, msg.obj.idx, msg.obj.name))
             po.save(fn)
         except:
@@ -2273,14 +2273,14 @@ class App(TkBrowser):
         ft = [\
             ('PO files', '.po'),
             ('all files', '.*')]
-        fn = filedialog.askopenfilename(parent = self, 
+        fn = filedialog.askopenfilename(parent = self,
             title = "Open translation for current part",
             filetypes = ft,
             initialdir = os.path.abspath(os.curdir))
         if not fn: return
         os.chdir(os.path.dirname(fn))
         self.open_tran_from(fn)
-            
+
     def open_tran_from(self, fn):
         self.tran_fn = fn
         try:
@@ -2307,7 +2307,7 @@ class App(TkBrowser):
             self.switch_view(0)
             self.clear_info()
             self.add_info("No messages or translations loaded")
-            return        
+            return
         fn = filedialog.asksaveasfilename(parent = self,
             title = "Save DIALOGUE.LOD",
             filetypes = [('DIALOGUE.LOD', ".lod"), ('all files', '.*')],
@@ -2318,7 +2318,7 @@ class App(TkBrowser):
             sim2 = petka.Engine()
             sim2.init_empty("cp1251")
             for msg in self.sim.msgs:
-                nmsg = petka.engine.MsgObject(msg.idx, msg.msg_wav, 
+                nmsg = petka.engine.MsgObject(msg.idx, msg.msg_wav,
                     msg.msg_arg1, msg.msg_arg2, msg.msg_arg3)
                 nmsg.name = self._t(msg.name, "msg")
                 sim2.msgs.append(nmsg)
@@ -2340,7 +2340,7 @@ class App(TkBrowser):
             self.switch_view(0)
             self.clear_info()
             self.add_info("No messages or translations loaded")
-            return        
+            return
         fn = filedialog.asksaveasfilename(parent = self,
             title = "Save NAMES.INI",
             filetypes = [('NAMES.INI', ".ini"), ('all files', '.*')],
@@ -2367,7 +2367,7 @@ class App(TkBrowser):
         print("Dumping pages")
         import json
         import urllib
-        
+
         def normalize_link(dest):
             self.dumpaddr = None
             def open_path(path):
@@ -2387,8 +2387,8 @@ class App(TkBrowser):
                     self.open_path = orig
             elif isinstance(dest, (list, tuple)):
                 dest = "/" + "/".join([str(x) for x in dest])
-            return dest            
-        
+            return dest
+
         def normalize_link_html(dest, curr):
             lnk = normalize_link(dest)
             if lnk[:1] == "/":
@@ -2397,7 +2397,7 @@ class App(TkBrowser):
             if rel == ".":
                 return rel
             return rel + ".html"
-        
+
         def save_data(fn, data, outline):
             print("  save " + fn)
             if not os.path.exists(path):
@@ -2453,28 +2453,28 @@ class App(TkBrowser):
                 f.write("</pre>\n")
 
                 f.write("</td></tr><table>\n")
-                f.write("</body></html>\n")                
-                
+                f.write("</body></html>\n")
+
         def save_curr():
             fn = "/".join([str(x) for x in self.curr_path])
             fn = os.path.join(path, fn)
             if not os.path.exists(os.path.dirname(fn)):
                 os.makedirs(os.path.dirname(fn))
-            save_data(fn, self.text_view.dump("0.0", "end-1c"), 
+            save_data(fn, self.text_view.dump("0.0", "end-1c"),
                 self.curr_lb_acts)
-            
+
         parsed = []
         queue = []
         def addaddr(dest):
             lnk = normalize_link(dest)
             if lnk not in parsed + queue:
                 queue.append(lnk)
-                
+
         def scan_page(dest):
             print("  scan " + str(dest))
             if dest.startswith("/parts/"): return # avoid changing part
             if dest.startswith("/files/"): return # avoid too big files data
-            #if dest.startswith("/res/"): return 
+            #if dest.startswith("/res/"): return
             if dest in parsed:
                 return
             parsed.append(dest)
@@ -2500,7 +2500,7 @@ class App(TkBrowser):
             addr = queue[0]
             queue = queue[1:]
             scan_page(addr)
-            
+
 
 def main():
     root = tkinter.Tk()
@@ -2527,6 +2527,6 @@ def main():
             argv = argv[1:]
     app.mainloop()
 
-    
+
 if __name__ == "__main__":
     main()
diff --git a/engines/petka/p12script.py b/engines/petka/p12script.py
index 107e272c7..389c96cc0 100755
--- a/engines/petka/p12script.py
+++ b/engines/petka/p12script.py
@@ -29,7 +29,7 @@ def find_in_folder(folder, name, ifnot = True):
 class P12Compiler:
     def __init__(self):
         pass
-        
+
     # =======================================================================
     # compiler utils
     # =======================================================================
@@ -62,7 +62,7 @@ class P12Compiler:
                             nitemesc = False
                             continue
                         nitem += ch
-                else:    
+                else:
                     if ch == "\"":
                         nmode = 1
                     elif ch == "#":
@@ -76,9 +76,9 @@ class P12Compiler:
 
             if len(nitem) > 0:
                 nline.append(nitem)
-                
+
             yield lineno, nline
-        
+
 
     def check8(self, value, name, lineno):
         if value == -1:
@@ -90,7 +90,7 @@ class P12Compiler:
             raise ScriptSyntaxError("Error at {}: value for \"{}\" too "\
                 "big - {} > 0xff".format(lineno, name, value))
         return value
-    
+
     def check16(self, value, name, lineno):
         if value == -1:
             return 0xffff
@@ -112,7 +112,7 @@ class P12Compiler:
             raise ScriptSyntaxError("Error at {}: value for \"{}\" too "\
                 "big - {} > 0xffffffff".format(lineno, name, value))
         return value
-    
+
     def convertnum(self, value):
         num = None
         if value[:2].upper() == "0X":
@@ -120,7 +120,7 @@ class P12Compiler:
                 num = int(value[2:], 16)
             except:
                 pass
-        else:                            
+        else:
             try:
                 num = int(value, 10)
             except:
@@ -133,7 +133,7 @@ class P12Compiler:
                 "already used at line {}".format(lineno, ident, \
                 self.usedid[ident][0]))
         self.usedid[ident] = [lineno, None]
-    
+
     def setidentvalue(self, ident, value):
         if self.usedid[ident][1] is None:
             self.usedid[ident][1] = value
@@ -141,10 +141,10 @@ class P12Compiler:
             raise ScriptSyntaxError("Redefine value for \"{}\" ({}) from "\
                 "\"{}\" to \"{}\"".format(ident, self.usedid[ident][0], \
                 self.usedid[ident][0], value))
-    
+
     def getidentvalue(self, ident):
         return self.usedid[ident][1]
-    
+
     def checkident(self, ident, name, lineno):
         if ident.upper() in self.reservedid:
             raise ScriptSyntaxError("Error at {}: identificator \"{}\" "\
@@ -190,7 +190,7 @@ class P12Compiler:
 
         pe = petka.Engine()
         pe.init_empty("cp1251")
-        
+
         mode = 0 # 0 - common, 1 - object/scene, 2 - on
         mode1tp = None
 
@@ -208,7 +208,7 @@ class P12Compiler:
         compitemused = {}
         # current action
         compact = None
-                   
+
         revOPS = {}
         for ok, ov in petka.OPCODES.items():
             revOPS[ov[0]] = ok
@@ -259,7 +259,7 @@ class P12Compiler:
                 else:
                     raise ScriptSyntaxError("Error at {}: unknown syntax "\
                         "\"{}\"".format(lineno, cmd))
-            elif mode == 1:                            
+            elif mode == 1:
                 # accept: REF, ON, ENDOBJ, ENDSCENE
                 cmd = tokens[0].upper()
                 if cmd == "REF" and mode1tp == "SCENE":
@@ -307,9 +307,9 @@ class P12Compiler:
                     mode = 2
                 elif cmd == "END" + mode1tp and len(tokens) == 1:
                     if mode1tp == "OBJ":
-                        compobj.append(compitem)                        
+                        compobj.append(compitem)
                     else:
-                        compscene.append(compitem)                        
+                        compscene.append(compitem)
                     compitem = None
                     # return
                     mode = 0
@@ -322,7 +322,7 @@ class P12Compiler:
                 cmd = tokens[0].upper()
                 if cmd == "ENDON" and len(tokens) == 1:
                     compitem["acts"].append(compact)
-                    compact = None                
+                    compact = None
                     mode = 1
                 else:
                     # check format
@@ -352,7 +352,7 @@ class P12Compiler:
         if mode != 0:
             raise ScriptSyntaxError("Error at {}: unfinished structure".\
                 format(lineno))
-    
+
         # second stage - RES
         # 1. capture res with numbers
         resused = {}
@@ -384,7 +384,7 @@ class P12Compiler:
         #            else:
         #                self.setidentvalue(ident, num)
         #                resused[num] = lineno
-        #                break                    
+        #                break
         #        if autoresnum <= 0:
         #            raise ScriptSyntaxError("Error at {}: out of resources "\
         #                "number".format(lineno))
@@ -404,7 +404,7 @@ class P12Compiler:
             if destfolder is not None:
                 f.close()
         print("RESOURCE.QRC saved: {} items".format(len(resused)))
-            
+
         # second stage - OBJ
         def makerec(citem):
             item = petka.engine.ScrObject(citem["num"], citem["name"])
@@ -416,14 +416,14 @@ class P12Compiler:
                     sonref = self.convertnum(act["sonref"])
                 if sonref is not None:
                     # direct number
-                    sonref = self.check16(sonref, "ON object ref", 
+                    sonref = self.check16(sonref, "ON object ref",
                         act["lineno"])
                 else:
                     # get by ident
                     self.checkident(act["sonref"], "ON object ref",
                         act["lineno"])
                     sonref = self.getidentvalue(act["sonref"])
-                onrec = petka.engine.ScrActObject(act["son"], 
+                onrec = petka.engine.ScrActObject(act["son"],
                     act["status"], sonref)
                 onrec.ops = []
                 for op in act["ops"]:
@@ -446,17 +446,17 @@ class P12Compiler:
                         fmt.append(("OP argument {}".format(i + 1),
                             self.check16, True))
                     argnum = self.convertargs(fmt, op["args"], op["lineno"])
-                    oprec =  petka.engine.ScrOpObject(opref, op["opcode"], 
+                    oprec =  petka.engine.ScrOpObject(opref, op["opcode"],
                         argnum[0], argnum[1], argnum[2])
                     onrec.ops.append(oprec)
                 item.acts.append(onrec)
             return item
-            
+
         for citem in compobj:
             objrec = makerec(citem)
             pe.objects.append(objrec)
             pe.obj_idx[objrec.idx] = objrec
-                  
+
         # second stage - SCENE
         backgrnd = []
         num_bkg = 0
@@ -518,10 +518,10 @@ class P12Compiler:
 
         mode = 0 # 0 - common, 1 - grp, 2 - act, 3 - dlg
                  # 10 - msg (autoreset to 0)
-                 
+
         # used identificators
         self.usedid = {}
-                   
+
         revOPS = {}
         for ok, ov in petka.OPCODES.items():
             revOPS[ov[0]] = ok
@@ -559,8 +559,8 @@ class P12Compiler:
                     while len(tokens) < 6:
                         tokens.append("0")
                     # check ident
-                    self.checkusedid(tokens[1], lineno)    
-                    self.setidentvalue(tokens[1], len(compmsg))                
+                    self.checkusedid(tokens[1], lineno)
+                    self.setidentvalue(tokens[1], len(compmsg))
                     # check wavfile name (1..12)
                     if len(tokens[2]) < 1 or len(tokens[2]) > 12:
                         raise ScriptSyntaxError("Error at {}: bad filename "\
@@ -613,7 +613,7 @@ class P12Compiler:
                                 "OPREF ""\"{}\" in ON".\
                                 format(lineno, tokens[1]))
                         don = self.check16(don, "ON opref", lineno)
-                    compactitem = {"don": don, "donref": tokens[2], 
+                    compactitem = {"don": don, "donref": tokens[2],
                         "args": tokens[3:], "dlgs": [], "lineno": lineno}
                 elif cmd == "ENDDLGGRP" and len(tokens) == 1:
                     mode = 0
@@ -680,7 +680,7 @@ class P12Compiler:
             else:
                 raise ScriptSyntaxError("Error at {}: unknown parser mode {}".\
                     format(lineno, mode))
-            
+
         # check unclosed objects
         if mode != 0:
             raise ScriptSyntaxError("Error at {}: unfinished structure".\
@@ -728,7 +728,7 @@ class P12Compiler:
                     donref = self.convertnum(act["donref"])
                 if donref is not None:
                     # direct number
-                    donref = self.check16(donref, "ON object ref", 
+                    donref = self.check16(donref, "ON object ref",
                         act["lineno"])
                 else:
                     # get by ident
@@ -753,7 +753,7 @@ class P12Compiler:
                             self.check32, True))
                     argnum = self.convertargs(fmt, dlg["args"], dlg["lineno"])
                     # build DLG
-                    dlgrec = petka.engine.DlgObject(len(pe.dlgops), 
+                    dlgrec = petka.engine.DlgObject(len(pe.dlgops),
                         argnum[0], argnum[1])
                     for op in dlg["dlgops"]:
                         fmt = [("DLGOP argument", self.check8, False),
@@ -840,7 +840,7 @@ class P12Compiler:
             if num in pe.scn_idx:
                 return "scene_{}".format(num)
             return self.fmtnum16(num)
-        
+
         def printres(resid):
             pprint("RES res_{} 0x{:x} \"{}\"".format(resid, resid,
                 self.escstr(pe.res[resid])))
@@ -857,7 +857,7 @@ class P12Compiler:
         def printitem(item, itemtype):
             pprint("{} {}_{} 0x{:x} \"{}\"".format(itemtype.upper(), itemtype,
                 item.idx, item.idx, self.escstr(item.name)))
-                
+
             # sub objects
             if itemtype == "scene":
                 if len(item.refs) == 0:
@@ -871,11 +871,11 @@ class P12Compiler:
                         pprint("  # unknown reference to 0x{:x}".format(
                            obj.idx))
                         ref = "0x{:x}".format(obj.idx)
-                    pprint("  REF {} {} {} {} {} {}".format(ref, 
-                        self.fmtnum32(a1), self.fmtnum32(a2), 
-                        self.fmtnum32(a3), self.fmtnum32(a4), 
+                    pprint("  REF {} {} {} {} {} {}".format(ref,
+                        self.fmtnum32(a1), self.fmtnum32(a2),
+                        self.fmtnum32(a3), self.fmtnum32(a4),
                         self.fmtnum32(a5)))
-            
+
             for act in item.acts:
                 actif = ""
                 if act.act_status != 0xff or act.act_ref != 0xffff:
@@ -897,14 +897,14 @@ class P12Compiler:
                         self.fmtnum16(op.op_arg2),
                         self.fmtnum16(op.op_arg3)))
                 pprint("  ENDON")
-        
+
             pprint("END{} # {}_{}".format(itemtype.upper(), itemtype, item.idx))
             pprint("")
-            
+
         pprint("# Decompile SCRIPT \"{}\"".format(scrname))
         pprint("# Version: {}".format(VERSION))
         pprint("# Encoding: {}".format(enc))
-        
+
         if decsort:
             for idx, scene in enumerate(pe.scenes):
                 pprint("# Scene {} / {}".format(idx + 1, len(pe.scenes)))
@@ -912,7 +912,7 @@ class P12Compiler:
                 if len(scene.idx.refs) > 0:
                     pprint("# referenced objects {}:".format(len(scene.refs)))
                     for ref in scene.refs:
-                        if ref[0].idx in used_obj: 
+                        if ref[0].idx in used_obj:
                             pprint("# object 0x{:x} already defined".\
                                 format(ref[0].idx))
                             continue
@@ -920,14 +920,14 @@ class P12Compiler:
                         if ref[0].idx in pe.obj_idx:
                             for act in ref[0].acts_array:
                                 for op in act.ops_array:
-                                    printrescheck(op.op_arg1, op.op_code)                
-                            printitem(obj, "obj")                
-                else:        
+                                    printrescheck(op.op_arg1, op.op_code)
+                            printitem(obj, "obj")
+                else:
                     pprint("# No referenced objects")
                 # display res
                 for act in scene.acts:
                     for op in act.ops:
-                        printrescheck(op.op_arg1, op.op_code)                
+                        printrescheck(op.op_arg1, op.op_code)
                 printitem(scene, "scene")
             # list unused
             msg = False
@@ -977,14 +977,14 @@ class P12Compiler:
                 msg.idx, msg.msg_wav, msg.msg_arg1, msg.msg_arg2, msg.msg_arg3))
             pprint(" \"{}\"".format(self.escstr(msg.name)))
             pprint("")
-    
+
         for gidx, grp in enumerate(pe.dlgs, 1):
             pprint("# {} = 0x{:x}".format(gidx, gidx))
             pprint("DLGGRP 0x{:x} {}".format(grp.idx, \
                 self.fmtnum32(grp.grp_arg1)))
             for sidx, act in enumerate(grp.acts, 1):
                 pprint("  ON {} 0x{:x} 0x{:x} 0x{:x} # {}".format(\
-                    self.fmtop(act.opcode), act.ref, act.arg1, 
+                    self.fmtop(act.opcode), act.ref, act.arg1,
                         act.arg2, sidx))
                 # print code
                 for didx, dlg in enumerate(act.dlgs, 1):
@@ -1094,27 +1094,27 @@ def checksame(f1, n1, f2, n2):
             format(f1))
         return True
     return False
-       
-       
+
+
 def action_dec(args):
     print("Decompile SCRIPT.DAT file")
     destpath = args.destpath
     encoding = args.encoding
-    
+
     if destpath:
         if ckeckoverwrite(destpath, args): return -1
-        if checksame(args.sourcepath, "source", destpath, "destination"): 
+        if checksame(args.sourcepath, "source", destpath, "destination"):
             return -2
         if not encoding:
             encoding = "UTF-8"
-        
+
     print("Input:\t{}".format(args.sourcepath))
     print("Output:\t{}".format(destpath or "-"))
     if destpath:
         print("Enc:\t{}".format(encoding))
     if args.decompile_sorted:
         print("Flag decompile_sorted enabled")
-    
+
     dcs = P12Compiler()
     if destpath:
         f = open(destpath, "wb")
@@ -1158,21 +1158,21 @@ def action_decd(args):
     print("Decompile DIALOGUE.FIX file")
     destpath = args.destpath
     encoding = args.encoding
-    
+
     if destpath:
         if ckeckoverwrite(destpath, args): return -1
         if checksame(args.sourcepath, "source", destpath, "destination"):
             return -2
         if not encoding:
             encoding = "UTF-8"
-        
+
     print("Input:\t{}".format(args.sourcepath))
     print("Output:\t{}".format(destpath or "-"))
     if destpath:
         print("Enc:\t{}".format(encoding))
     if args.verbose:
         print("Flag verbose enabled")
-    
+
     dcs = P12Compiler()
     if destpath:
         f = open(destpath, "wb")
@@ -1230,13 +1230,13 @@ def internaltest(folder):
         hm = hashlib.md5()
         hm.update(mem.read())
         return hm.hexdigest() == hf.hexdigest()
-            
+
     for test in test_arr:
         print("=== Test: " + test + " ===")
         testbase = os.path.join(folder, test)
         path = find_in_folder(testbase, "script.dat")
         dcs = P12Compiler()
-        mems = io.BytesIO()        
+        mems = io.BytesIO()
         dcs.pretty_print_scr(path, mems)
         print("Decompiled script:", mems.tell())
         # compile back
@@ -1261,8 +1261,8 @@ def internaltest(folder):
         if not os.path.exists(path):
             print("All ok - no dialogue")
             continue
-            
-        mems = io.BytesIO()        
+
+        mems = io.BytesIO()
         dcs.pretty_print_dlg(path, mems)
         print("Decompiled dialogue:", mems.tell())
         # compile back
@@ -1284,23 +1284,23 @@ def internaltest(folder):
 
 def action_version(args):
     print("Version: " + VERSION)
-    
+
 def main():
     print(APPNAME + ", " + VERSION)
     print("\tRoman Kharin (romiq.kh at gmail.com)")
     if len(sys.argv) < 2:
         print("Use -h for help.")
         return
-        
+
     if len(sys.argv) >= 3:
         if sys.argv[1] == "test":
             internaltest(sys.argv[2])
             return
-    
+
     parser = argparse.ArgumentParser(epilog = \
         "For actions help try: <action> -h")
     subparsers = parser.add_subparsers(title = 'actions')
-    
+
     # decompile - <script.dat> [[--enc <encoding>] -o <decompiled.txt>]
     parser_dec = subparsers.add_parser("decompile", aliases = ['d'], \
         help = "decompile script.dat")
diff --git a/engines/petka/petka/__init__.py b/engines/petka/petka/__init__.py
index 7c2864ce2..f552ef9c6 100644
--- a/engines/petka/petka/__init__.py
+++ b/engines/petka/petka/__init__.py
@@ -8,4 +8,3 @@ from .imgflc import FLCLoader
 from .imgleg import LEGLoader
 from .imgmsk import MSKLoader
 from .saves import SaveLoader
-
diff --git a/engines/petka/petka/engine.py b/engines/petka/petka/engine.py
index afe725c62..18d524af4 100644
--- a/engines/petka/petka/engine.py
+++ b/engines/petka/petka/engine.py
@@ -88,7 +88,7 @@ ACTIONS = {
     4: ("TALK",    5006),
     5: ("CHAPAY",  5007),
     6: ("INVNTR",    -1),
-    
+
 }
 
 class ScrObject:
@@ -157,7 +157,7 @@ class DlgOpObject:
         self.ref = ref          # message idx
         self.msg = None         # message
         self.pos = None
-        
+
 class Engine:
     def __init__(self):
         self.fman = None
@@ -168,11 +168,11 @@ class Engine:
 
         self.curr_part = None
         self.curr_chap = None
-        
+
         self.curr_path = None
         self.curr_speech = None
         self.curr_diskid = None
-        
+
     def init_empty(self, enc):
         self.enc = enc
         self.objects = []
@@ -183,7 +183,7 @@ class Engine:
         self.dlgs = []
         self.dlg_idx = {}
         self.dlgops = []
-                
+
     def parse_ini(self, f):
         # parse ini settings
         curr_sect = None
@@ -210,7 +210,7 @@ class Engine:
         ini["__ordersect__"] = order_sect
         ini["__order__"] = orders
         return ini
-        
+
     def parse_res(self, f):
         res  = {}
         resord = []
@@ -228,7 +228,7 @@ class Engine:
             res[res_id] = value
             resord.append(res_id)
         return res, resord
-        
+
     def load_data(self, folder, enc):
         self.init_empty(enc)
         self.fman = FileManager(folder)
@@ -252,7 +252,7 @@ class Engine:
         else:
             # load BGS.INI only (e.g. DEMO)
             self.parts_ini = None
-            
+
         # std stores
         self.fman.load_store("patch.str")
         self.fman.load_store("main.str")
@@ -278,7 +278,7 @@ class Engine:
             self.curr_path = ""
             self.curr_speech = ""
             self.curr_diskid = None
-        
+
         # load .STR
         strs = ["Flics", "Background", "Wav", "Music", "SFX", "Speech"]
         for strf in strs:
@@ -292,7 +292,7 @@ class Engine:
         self.load_names()
         # load dialogs
         self.load_dialogs()
-        
+
         # current state
         self.curr_scene = None
         self.curr_obj = None
@@ -301,13 +301,13 @@ class Engine:
         self.curr_char1 = None
         self.curr_char2 = None
         self.curr_invntr = None
-        
+
     def load_script(self, scrname = None, bkgname = None, resname = None):
         self.objects = []
         self.scenes = []
         self.obj_idx = {}
         self.scn_idx = {}
-        
+
         if scrname is None:
             try:
                 data = self.fman.read_file(self.curr_path + "script.dat")
@@ -347,7 +347,7 @@ class Engine:
             rec = ScrObject(obj_id, name)
             rec.acts = acts
             return off, rec
-        
+
         for i in range(num_obj):
             off, obj = read_rec(off)
             self.objects.append(obj)
@@ -358,9 +358,9 @@ class Engine:
             scn.refs = None
             self.scenes.append(scn)
             self.scn_idx[scn.idx] = scn
-            
+
         if bkgname is None:
-            try:    
+            try:
                 data = self.fman.read_file(self.curr_path + "backgrnd.bg")
             except:
                 data = None
@@ -374,7 +374,7 @@ class Engine:
             finally:
                 f.close()
 
-        if data:        
+        if data:
             num_rec = struct.unpack_from("<I", data[:4])[0]
         else:
             num_rec = 0
@@ -383,7 +383,7 @@ class Engine:
             scn_ref, num_ref = struct.unpack_from("<HI", data[off:off + 6])
             off += 6
             if scn_ref in self.scn_idx:
-                scn = self.scn_idx[scn_ref]    
+                scn = self.scn_idx[scn_ref]
                 scn.refs = []
             else:
                 raise EngineError("DEBUG: Scene ID = 0x{:x} not found".\
@@ -398,7 +398,7 @@ class Engine:
                 else:
                     raise EngineError("DEBUG: Scene ref 0x{:x} not found".\
                         format(obj[0]))
-                        
+
         if resname is None:
             try:
                 f = self.fman.read_file_stream(self.curr_path + "resource.qrc")
@@ -409,8 +409,8 @@ class Engine:
                 f = open(resname, "rb")
             except:
                 f = None
-        try:            
-            if f:            
+        try:
+            if f:
                 self.res, self.resord = self.parse_res(f)
             else:
                 self.res = {}
@@ -418,7 +418,7 @@ class Engine:
         finally:
             if f:
                 f.close()
-        
+
     def load_names(self):
         self.names = {}
         self.namesord = []
@@ -463,7 +463,7 @@ class Engine:
                 except:
                     r, g, b = 255, 255, 255
                 obj.cast = (r, g, b)
-            
+
     def load_bgs(self):
         # load BGS.INI
         self.bgs_ini = {}
@@ -475,7 +475,7 @@ class Engine:
                 self.bgs_ini = self.parse_ini(f)
             finally:
                 f.close()
-        
+
         settings = self.bgs_ini.get("Settings", {})
         self.start_scene = settings.get("StartRoom", None)
 
@@ -511,14 +511,14 @@ class Engine:
             except:
                 persp = None
             scene.persp = persp
-    
-            
+
+
     def load_dialogs(self, fixname = None, lodname = None, noobjref = False):
         self.msgs = []
         # DIALOGUES.LOD
-        
+
         if lodname is None:
-            try:    
+            try:
                 f = self.fman.read_file_stream(self.curr_path + "dialogue.lod")
             except:
                 f = None
@@ -556,7 +556,7 @@ class Engine:
         self.dlg_idx = {}
         self.dlgops = []
         if fixname is None:
-            try:    
+            try:
                 f = self.fman.read_file_stream(self.curr_path + "dialogue.fix")
             except:
                 f = None
@@ -624,13 +624,13 @@ class Engine:
                             dlg.ops = oparr
                             oparr = []
                         dlg = opref[idx]
-                    oparr.append(oprec)    
+                    oparr.append(oprec)
                 if len(oparr) > 0:
                     dlg.ops = oparr
             finally:
                 if f:
                     f.close()
-                
+
     def write_script(self, f):
         f.write(struct.pack("<II", len(self.objects), len(self.scenes)))
 
@@ -640,24 +640,24 @@ class Engine:
             f.write(ename)
             f.write(struct.pack("<I", len(rec.acts)))
             for act in rec.acts:
-                f.write(struct.pack("<HBHI", act.act_op, act.act_status, 
+                f.write(struct.pack("<HBHI", act.act_op, act.act_status,
                     act.act_ref, len(act.ops)))
                 for op in act.ops:
                     f.write(struct.pack("<5H", op.op_ref, op.op_code,
                         op.op_arg1, op.op_arg2, op.op_arg3))
-        
+
         for rec in self.objects + self.scenes:
             write_rec(rec)
-        
+
     def write_backgrnd(self, f):
         lst = [scn for scn in self.scenes if scn.refs is not None]
         f.write(struct.pack("<I", len(lst)))
         for scn in lst:
             f.write(struct.pack("<HI", scn.idx, len(scn.refs)))
             for ref in scn.refs:
-                f.write(struct.pack("<H5I", ref[0].idx, ref[1], ref[2], 
+                f.write(struct.pack("<H5I", ref[0].idx, ref[1], ref[2],
                     ref[3], ref[4], ref[5]))
-                
+
 
     def write_lod(self, f):
         f.write(struct.pack("<I", len(self.msgs)))
@@ -665,7 +665,7 @@ class Engine:
             wav = msg.msg_wav.encode(self.enc)
             while len(wav) < 12:
                 wav += b"\0"
-            f.write(struct.pack("<I12sII", msg.msg_arg1, wav, msg.msg_arg2, 
+            f.write(struct.pack("<I12sII", msg.msg_arg1, wav, msg.msg_arg2,
                 msg.msg_arg3))
         for msg in self.msgs:
             txt = msg.name.encode(self.enc)
@@ -677,11 +677,11 @@ class Engine:
             f.write(struct.pack("<3I", grp.idx, len(grp.acts), grp.grp_arg1))
         for grp in self.dlgs:
             for act in grp.acts:
-                f.write(struct.pack("<2H3I", act.opcode, act.ref, 
+                f.write(struct.pack("<2H3I", act.opcode, act.ref,
                     len(act.dlgs), act.arg1, act.arg2))
             for act in grp.acts:
                 for dlg in act.dlgs:
-                    f.write(struct.pack("<3I", dlg.op_start, dlg.arg1, 
+                    f.write(struct.pack("<3I", dlg.op_start, dlg.arg1,
                         dlg.arg2))
         f.write(struct.pack("<I", len(self.dlgops)))
         for op in self.dlgops:
@@ -693,23 +693,23 @@ class Engine:
     # create current state from initial state
     def init_game(self):
         self.curr_scene = self.scene_to_id(self.start_scene)
-        
+
         self.curr_obj = []
         for obj in self.objects + self.scenes:
             state = ScrObjectState(obj)
             self.curr_obj.append(state)
             state.prop
-            
+
         self.curr_dlg = []
         for dlgop in self.dlgops:
             dlgstate = DlgOpObject(dlgop.code, dlgop.arg, dlgop.ref)
             dlgstate.pos = dlgop.pos
             self.curr_dlg.append(dlgstate)
-        
+
         self.curr_cursor = None
         self.curr_char1 = None
         self.curr_char2 = None
-        self.curr_invntr = []        
+        self.curr_invntr = []
 
     def load_save(self, ls):
         self.curr_scene = self.scene_to_id(ls.scene)
@@ -720,4 +720,3 @@ class Engine:
         self.curr_char1 = None
         self.curr_char2 = None
         self.curr_invntr = None
-
diff --git a/engines/petka/petka/fman.py b/engines/petka/petka/fman.py
index e83d68682..e69f1867f 100644
--- a/engines/petka/petka/fman.py
+++ b/engines/petka/petka/fman.py
@@ -12,11 +12,11 @@ from . import EngineError
 class FileManager:
     def __init__(self, root):
         self.root = os.path.abspath(root)
-    
+
         self.strfd = []
         self.strtable = {}
         self.strtableord = []
-   
+
     def find_path(self, path):
         # search case insensive from root
         dpath = []
@@ -32,7 +32,7 @@ class FileManager:
                 break
             if not ok: return None
         return npath
-    
+
     def load_store(self, name, tag = 0):
         path = self.find_path(name)
         if path is None:
@@ -54,7 +54,7 @@ class FileManager:
         index_len = struct.unpack_from("<I", temp)[0]
         index_table = []
         for iref in range(index_len):
-            temp = f.read(12) 
+            temp = f.read(12)
             data = struct.unpack_from("<III", temp)
             index_table.append((data[1], data[2]))
         strlst = []
@@ -93,21 +93,21 @@ class FileManager:
             finally:
                 f.close()
             return data
-            
+
     def read_file_stream(self, fname):
         data = self.read_file(fname)
         mems = io.BytesIO()
         mems.write(data)
         mems.seek(0)
-        return mems        
-            
+        return mems
+
     def exists(self, fname):
         sf = fname.lower().replace("\\", "/")
         if sf in self.strtable:
             return True
         else:
-            return self.find_path(fname) is not None       
-        
+            return self.find_path(fname) is not None
+
     def unload_stores(self, flt = None):
         strfd = []
         strtable = {}
@@ -129,5 +129,3 @@ class FileManager:
         self.strfd = strfd
         self.strtable = strtable
         self.strtableord = strtableord
-        
-        
diff --git a/engines/petka/petka/imgbmp.py b/engines/petka/petka/imgbmp.py
index 26e8261ba..dc8e40904 100644
--- a/engines/petka/petka/imgbmp.py
+++ b/engines/petka/petka/imgbmp.py
@@ -17,18 +17,18 @@ class BMPLoader:
         self.image = None
         self.width = 0
         self.height = 0
-        
+
     def load_data_int16(self, f):
         # check magic string "BM"
         temp = f.read(2)
         if temp != b"BM":
             raise EngineError("Bad magic string")
         off = 2
-        
+
         temp = f.read(12)
         f_sz, res1, res2, data_offset = struct.unpack_from("<IHHI", temp)
         off += 12
-        
+
         # read next 40 bytes, BITMAPINFOHEADER
         temp = f.read(40)
         pict = struct.unpack_from("<IiiHHIIiiII", temp)
@@ -37,7 +37,7 @@ class BMPLoader:
             raise EngineError("Unsupported InfoHeader")
         pictw = pict[1]
         picth = pict[2]
-        
+
         # read data_offset - 40 - 6 bytes
         delta = data_offset - 40 - 6
         if delta < 0:
@@ -50,7 +50,7 @@ class BMPLoader:
 
         bsz = pictw * picth * 2
         picture_data = f.read(bsz)
- 
+
         off += bsz
         if len(picture_data) != bsz:
             raise EngineError("Bitmap truncated, need {}, got {}".format(bsz, \
@@ -66,9 +66,9 @@ class BMPLoader:
         if len(temp) > 0:
             if temp != b"\x00\x00":
                 raise EngineError("BMP read error, some data unparsed")
-                
+
         return pictw, picth, picture_data
-        
+
     def pixelswap16ud(self, pw, ph, pd):
         # convert 16 bit to 24 + vertical reverse
         b16arr = array.array("H") # unsigned short
@@ -107,22 +107,22 @@ class BMPLoader:
         except:
             f.seek(0)
             self.image = Image.open(f)
-        
+
     def load_raw(self, pw, ph, pd):
         if Image:
             pd = self.pixelswap16(pw, ph, pd).tobytes()
-            self.image = Image.frombytes("RGB", (pw, ph), pd) 
+            self.image = Image.frombytes("RGB", (pw, ph), pd)
         else:
             self.width = pw
             self.height = ph
             self.rgb = self.pixelswap16(pw, ph, pd)
-        
+
     def load_data(self, f):
         try:
             pw, ph, pd = self.load_data_int16(f)
             if Image:
                 pd = self.pixelswap16ud(pw, ph, pd).tobytes()
-                self.image = Image.frombytes("RGB", (pw, ph), pd) 
+                self.image = Image.frombytes("RGB", (pw, ph), pd)
             else:
                 self.width = pw
                 self.height = ph
@@ -130,4 +130,3 @@ class BMPLoader:
         except:
             f.seek(0)
             self.image = Image.open(f)
-
diff --git a/engines/petka/petka/imgflc.py b/engines/petka/petka/imgflc.py
index 1af7377a2..9da0b4ff7 100644
--- a/engines/petka/petka/imgflc.py
+++ b/engines/petka/petka/imgflc.py
@@ -52,7 +52,7 @@ class FLCLoader:
         self.height = 0
         self.frame_num = 0
         self.delay = 0
-        
+
 
     def load_info(self, f):
         self.image = Image.open(f)
@@ -63,23 +63,23 @@ class FLCLoader:
                 self.frame_num += 1
         except EOFError:
             pass # end of sequence
-        
-        
+
+
     def parseflcchunks(self, f, offset, limit, level = 0, maxchunks = None,):
         def check_hdr(size, delta, name, offset):
             if delta < size:
                 raise EngineError("Incorrect FLC %s chunk at 0x{:08x}".format(
                     (name, offset)))
-        
+
         chunks = []
         while True:
             if limit is not None:
-                if offset >= limit: 
+                if offset >= limit:
                     break
             if maxchunks is not None:
-                if len(chunks) >= maxchunks: 
+                if len(chunks) >= maxchunks:
                     break
-            chunk = {"offset": offset}  
+            chunk = {"offset": offset}
             temp = f.read(6)
             sz, tp = struct.unpack_from("<IH", temp)
             offset += 6
@@ -90,7 +90,7 @@ class FLCLoader:
             if delta < 0:
                 raise EngineError("Incorrect FLC chunk at 0x{:08x}".format(
                     chunk["offset"]))
-            
+
             raw_chunks = [
                 0x4, # COLOR_256
                 0x7, # DELTA_FLC
@@ -114,7 +114,7 @@ class FLCLoader:
                 delta -= 6
                 height, width, xlate = struct.unpack_from("<3H", temp)
                 offset += 6
-                offset, subchunks = self.parseflcchunks(f, offset, 
+                offset, subchunks = self.parseflcchunks(f, offset,
                     offset + delta, level + 1, 1)
                 chunk["chunks"] = subchunks
                 #print(subchunks)
@@ -129,17 +129,17 @@ class FLCLoader:
                 chunk["delay"] = delay
                 chunk["width"] = width
                 chunk["height"] = height
-                offset, subchunks = self.parseflcchunks(f, offset, 
+                offset, subchunks = self.parseflcchunks(f, offset,
                     offset + delta, level + 1, sub_num)
                 chunk["chunks"] = subchunks
             else:
                 raise Exception("Unknown FLC chunk type 0x{:04x} at 0x{:x08x}".\
                     format(tp, offset))
-            
+
             chunks.append(chunk)
-            
+
         return offset, chunks
-        
+
     def load_data(self, f):
         # parse header
         offset = 0
@@ -151,32 +151,32 @@ class FLCLoader:
                 hdr_struct += htp
             else:
                 hdr_struct += "%d" % hsz + htp
-        
+
         header = {}
         temp = f.read(128)
         hdr = struct.unpack_from(hdr_struct, temp)
-        
+
         offset += 128
-            
+
         if len(hdr) != len(hdr_keys):
             raise EngineError("Incorrect FLC header {} != {}".format(
                 len(hdr), len(hdr_keys)))
         for hid in range(len(hdr)):
             header[hdr_keys[hid]] = hdr[hid]
-        
+
         if header["ftype"] != 0xAF12:
             raise EngineError("Unsupported FLC type (0x{:04x})".format(
                 header["ftype"]))
-        
+
         # check if not EGI ext
         if header["creator"] == 0x45474900:
             if header["ext_flags"] != 0:
-                raise EngineError("Unsupported FLC EGI extension")    
-        
+                raise EngineError("Unsupported FLC EGI extension")
+
         # NOTE: we recreate FLC to avoid Pilllow bug
         #  1. remove 0xf100 chunk  (PREFIX, implementation specific)
         #  2. remobe 0x12 subchunk (PSTAMP) from 1st frame
-        
+
         # read chunks
         _, chunks = self.parseflcchunks(f, offset, header["fsize"])
 
@@ -189,7 +189,7 @@ class FLCLoader:
             elif chunk["type"] == 0xF1FA:
                 rebuild = False
                 nchunks = []
-                nsz = 16 # I6H - type, size, sub_num, delay, 
+                nsz = 16 # I6H - type, size, sub_num, delay,
                          # reserved, width, height
                 for schunk in chunk["chunks"]:
                     if schunk["type"] == 0x12: # detect mailformed PSTAMP
@@ -198,7 +198,7 @@ class FLCLoader:
                         nchunks.append(schunk)
                         nsz += schunk["size"]
                 if rebuild:
-                    buf.write(struct.pack("<I6H", nsz, 0xF1FA, len(nchunks), 
+                    buf.write(struct.pack("<I6H", nsz, 0xF1FA, len(nchunks),
                         chunk["delay"], 0, chunk["width"], chunk["height"]))
                     for schunk in nchunks:
                         f.seek(schunk["offset"])
@@ -209,4 +209,3 @@ class FLCLoader:
 
         buf.seek(0)
         self.image = Image.open(buf)
-
diff --git a/engines/petka/petka/imgleg.py b/engines/petka/petka/imgleg.py
index cc6c9398e..ee340e56b 100644
--- a/engines/petka/petka/imgleg.py
+++ b/engines/petka/petka/imgleg.py
@@ -9,10 +9,10 @@ from . import EngineError
 class LEGLoader:
     def __init__(self):
         self.coords = []
-        
+
     def load_info(self, f):
         return self.load_data(f)
-        
+
     def load_data(self, f):
         hdr = f.read(4)
         if hdr != b"xyof":
@@ -21,7 +21,6 @@ class LEGLoader:
         rest = f.read()
         if len(rest) % 8:
             raise EngineError("Bad LEG/OFF size {}".format(len(rest)))
-            
+
         sf = struct.unpack("<{}l".format(len(rest) // 4), rest)
         self.coords = [[sf[i * 2], sf[i * 2 + 1]] for i in range(len(rest) // 8)]
-
diff --git a/engines/petka/petka/imgmsk.py b/engines/petka/petka/imgmsk.py
index cfd20dd9f..239c65616 100644
--- a/engines/petka/petka/imgmsk.py
+++ b/engines/petka/petka/imgmsk.py
@@ -10,10 +10,10 @@ class MSKLoader:
     def __init__(self):
         self.bound = [0, 0, 0, 0]
         self.rects = []
-        
+
     def load_info(self, f):
         return self.load_data(f)
-        
+
     def load_data(self, f):
         rest = f.read()
         f.seek(0)
@@ -31,10 +31,10 @@ class MSKLoader:
                 rec.append([l, t, r, b])
             rects.append(rec)
             delta -= 4
-            
+
         if delta != 0:
            raise EngineError("Bad MSK file")
-            
+
         temp = f.read(len(rects) * 4)
         frms = struct.unpack_from("<{}I".format(len(rects)), temp)
 
@@ -45,4 +45,3 @@ class MSKLoader:
         self.bound = struct.unpack_from("<4i", temp)
 
         self.rects = list(zip(reversed(frms), rects))
-
diff --git a/engines/petka/petka/saves.py b/engines/petka/petka/saves.py
index 2f5ebda0c..9fcea40d6 100644
--- a/engines/petka/petka/saves.py
+++ b/engines/petka/petka/saves.py
@@ -15,7 +15,7 @@ class SaveLoader:
         self.stamp = None
         self.enc = enc
         self.objects = []
-       
+
     def load_data(self, f, part, objnum):
         data = f.read(8)
         self.part, self.chap = struct.unpack("<2I", data)
@@ -25,7 +25,7 @@ class SaveLoader:
         stamp = f.read(30)
         stamp = stamp.split(b"\x00")[0]
         self.stamp = stamp.decode(self.enc)
-        
+
         # read screenshot
         sl = 108 * 81 * 2
         rgb = f.read(sl)
@@ -33,24 +33,24 @@ class SaveLoader:
             raise EngineError("Bad SAVE length (no screenshot)")
         self.shot = BMPLoader()
         self.shot.load_raw(108, 81, rgb)
-               
+
         hz2 = f.read(216)
         if hz2 != b"\x00" * 216:
             raise EngineError("Bad SAVE error in HZ2 field")
-            
+
         data = f.read(4)
         hz3 = struct.unpack("<I", data)[0]
 
         if hz3 != objnum + 3:
             raise EngineError("Bad SAVE objects number")
-        
+
         def readstr():
             data = f.read(4)
             strlen = struct.unpack("<I", data)[0]
             s = f.read(strlen)
             #print("STR", strlen, data, s)
             return s.decode(self.enc)
-        
+
         for i in range(objnum):
             s1 = readstr()
             s2 = readstr()
@@ -61,8 +61,8 @@ class SaveLoader:
             obj["res"] = recs[2]
             self.objects.append(obj)
             #print(i, s1, s2)
-            
-        # invntr        
+
+        # invntr
         data = f.read(4)
         invlen = struct.unpack("<I", data)[0]
         data = f.read(invlen * 2)
@@ -70,11 +70,11 @@ class SaveLoader:
 
         # scene
         self.scene = readstr()
-        
+
         # char positions
         data = f.read(16)
         charpos = struct.unpack("<4I", data)
-        
+
         # arr dialog opcodes
         data = f.read(4)
         dlgoplen = struct.unpack("<I", data)[0]
@@ -83,7 +83,7 @@ class SaveLoader:
             data = f.read(4)
             ref, arg, code = struct.unpack_from("<HBB", data)
             self.dlgops.append([code, arg, ref])
-        
+
         data = f.read(20)
         self.cursor_res, self.cursor, self.cursor_obj, c1res, c2res = \
             struct.unpack("<5I", data)
@@ -98,5 +98,3 @@ class SaveLoader:
 
         if f.read():
             raise EngineError("Bad SAVE length (extra data)")
-
-
diff --git a/engines/petka/testtkgui.py b/engines/petka/testtkgui.py
index 56a953a71..ff43aee6d 100755
--- a/engines/petka/testtkgui.py
+++ b/engines/petka/testtkgui.py
@@ -15,8 +15,8 @@ VERSION = "v0.1 2015-06-20"
 
 class App(TkBrowser):
     def __init__(self, master):
-        super().__init__(master)            
-                
+        super().__init__(master)
+
     def init_gui(self):
         self.master.title(APPNAME)
         # path
@@ -25,7 +25,7 @@ class App(TkBrowser):
         else:
             self.app_path = __file__
         self.app_path = os.path.abspath(os.path.dirname(self.app_path))
-                
+
     def create_widgets(self):
         super().create_widgets()
 
@@ -40,7 +40,7 @@ class App(TkBrowser):
                     repath = ""
                     break
         if repath:
-            self.open_path(repath)       
+            self.open_path(repath)
 
 
     def create_menu(self):
@@ -51,7 +51,7 @@ class App(TkBrowser):
         self.menufile.add_separator()
         self.menufile.add_command(
                 command = self.on_exit,
-                label = "Quit")    
+                label = "Quit")
 
     def path_default(self, path):
         self.switch_view(0)
@@ -64,14 +64,14 @@ class App(TkBrowser):
         self.add_info("\n\n<b>This is multi-")
         self.add_info("line\nbold</b> text\n")
         self.add_info("\n\n<u>This is multi-")
-        self.add_info("line\nunderline</u> text\n")       
+        self.add_info("line\nunderline</u> text\n")
         self.add_info("\n\n<font color=\"#ff00FF\">This is multi-")
         self.add_info("line\ncolor</font> text\n")
 
         self.add_info("\n<font bg=\"red\" color=\"white\"> <b>White</b> on <i>red</i> </font>\n")
 
-        self.insert_lb_act("Testing", "/test")        
-        return True        
+        self.insert_lb_act("Testing", "/test")
+        return True
 
     def path_test(self, path):
         if len(path) > 1:
@@ -84,7 +84,7 @@ class App(TkBrowser):
             self.insert_lb_act("Image", "/test/image")
             self.switch_view(0)
             self.clear_info()
-            self.add_info("Select test from outline")            
+            self.add_info("Select test from outline")
         return True
 
     def path_test_item(self, path):
@@ -114,13 +114,13 @@ class App(TkBrowser):
                         self.gl_state.get("test.info.mode", None)))
                     for i in range(100):
                         self.add_info("  Item {}\n".format(i))
-            
+
         if self.last_path[:2] != ("test", path[1]):
             self.update_gui("Test %s" % path[1])
             self.insert_lb_act("Testing", "/test")
             self.insert_lb_act("-", None)
             for i in range(15):
-                self.insert_lb_act("{} #{}".format(path[1], i), 
+                self.insert_lb_act("{} #{}".format(path[1], i),
                     path[:2] + (i,), i)
             # create mode buttons
             def sw_mode1():
@@ -165,6 +165,6 @@ def main():
             app.start_act.append(["open", argv[0]])
             argv = argv[1:]
     app.mainloop()
-    
+
 if __name__ == "__main__":
     main()
diff --git a/engines/petka/tkguibrowser.py b/engines/petka/tkguibrowser.py
index fe17d6d9a..a5d05cb7c 100644
--- a/engines/petka/tkguibrowser.py
+++ b/engines/petka/tkguibrowser.py
@@ -44,10 +44,10 @@ def fmt_arg(value):
         return "-1"
     else:
         return "0x{:X}".format(value)
-    
+
 def fmt_dec(value, add = 0):
     return "{{:{}}}".format(fmt_dec_len(value, add))
-        
+
 def fmt_dec_len(value, add = 0):
     if value == 0:
         d = 1
@@ -55,7 +55,7 @@ def fmt_dec_len(value, add = 0):
         d = int(math.log10(value)) + 1
     d += add
     return d
-        
+
 # thanx to http://effbot.org/zone/tkinter-text-hyperlink.htm
 class HyperlinkManager(HTMLParser):
 
@@ -151,7 +151,7 @@ class HyperlinkManager(HTMLParser):
                 self.parser_tags.append(self.bg(bg))
             else:
                 self.parser_tags.append(self.color(color))
-    
+
     def handle_endtag(self, tag):
         self.parser_tags = self.parser_tags[:-1]
 
@@ -184,7 +184,7 @@ class TkBrowser(tkinter.Frame):
 
     def __init__(self, master):
         tkinter.Frame.__init__(self, master)
-        self.pack(fill = tkinter.BOTH, expand = 1)        
+        self.pack(fill = tkinter.BOTH, expand = 1)
         self.pad = None
 
         # gui
@@ -210,9 +210,9 @@ class TkBrowser(tkinter.Frame):
         self.main_image = tkinter.PhotoImage(width = 1, height = 1)
         # add on_load handler
         self.after_idle(self.on_first_display)
-    
+
     def init_gui(self):
-        pass    
+        pass
 
     def update_after(self):
         if not self.need_update:
@@ -267,20 +267,20 @@ class TkBrowser(tkinter.Frame):
         ]
         for text, cmd in btns:
             if text is None:
-                frm = ttk.Frame(self.toolbar, width = self.pad, 
+                frm = ttk.Frame(self.toolbar, width = self.pad,
                     height = self.pad)
                 frm.pack(side = tkinter.LEFT)
-                continue            
+                continue
             btn = ttk.Button(self.toolbar, text = text, \
                 style = "Tool.TButton", command = cmd)
             btn.pack(side = tkinter.LEFT)
         frm = ttk.Frame(self.toolbar, width = self.pad, height = self.pad)
         frm.pack(side = tkinter.LEFT)
-        
+
         # main panel
         self.pan_main = ttk.PanedWindow(self, orient = tkinter.HORIZONTAL)
         self.pan_main.pack(fill = tkinter.BOTH, expand = 1)
-        
+
         # leftpanel
         self.frm_left = ttk.Frame(self.pan_main)
         self.pan_main.add(self.frm_left)
@@ -289,7 +289,7 @@ class TkBrowser(tkinter.Frame):
         self.pan_main.add(self.frm_view)
         self.frm_view.grid_rowconfigure(0, weight = 1)
         self.frm_view.grid_columnconfigure(0, weight = 1)
-        self.scr_view_x = ttk.Scrollbar(self.frm_view, 
+        self.scr_view_x = ttk.Scrollbar(self.frm_view,
             orient = tkinter.HORIZONTAL)
         self.scr_view_x.grid(row = 1, column = 0, \
             sticky = tkinter.E + tkinter.W)
@@ -297,22 +297,22 @@ class TkBrowser(tkinter.Frame):
         self.scr_view_y.grid(row = 0, column = 1, sticky = \
             tkinter.N + tkinter.S)
         # canvas
-        self.canv_view = tkinter.Canvas(self.frm_view, height = 150, 
-            bd = 0, highlightthickness = 0, 
+        self.canv_view = tkinter.Canvas(self.frm_view, height = 150,
+            bd = 0, highlightthickness = 0,
             scrollregion = (0, 0, 50, 50),
             )
         # don't forget
         #   canvas.config(scrollregion=(left, top, right, bottom))
         self.canv_view.bind('<Configure>', self.on_resize_view)
         self.canv_view.bind('<ButtonPress-1>', self.on_mouse_view)
-        
+
         # text
         self.text_view = ReadOnlyText(self.frm_view,
             highlightthickness = 0,
             )
         self.text_hl = HyperlinkManager(self.text_view)
         self.text_view.bind('<Configure>', self.on_resize_view)
-        
+
     def create_menu(self):
         self.menubar = tkinter.Menu(self.master)
         self.master.configure(menu = self.menubar)
@@ -322,10 +322,10 @@ class TkBrowser(tkinter.Frame):
 
     def on_mouse_view(self, event):
         self.update_after()
-        
+
     def on_resize_view(self, event):
         self.update_after()
- 
+
     def parse_path(self, loc):
         if isinstance(loc, str):
             path = []
@@ -342,8 +342,8 @@ class TkBrowser(tkinter.Frame):
         path = tuple(path)
         while path[-1:] == ("",):
             path = path[:-1]
-        return path    
- 
+        return path
+
     def desc_path(self, loc):
         path = self.parse_path(loc)
         if len(path) > 0:
@@ -362,11 +362,11 @@ class TkBrowser(tkinter.Frame):
         c = self.canv_view
         c.delete(tkinter.ALL)
 
-        w = self.canv_view.winfo_width() 
+        w = self.canv_view.winfo_width()
         h = self.canv_view.winfo_height()
-        if (w == 0) or (h == 0): 
+        if (w == 0) or (h == 0):
             return
-        
+
         scale = 0
 
         # Preview image
@@ -429,7 +429,7 @@ class TkBrowser(tkinter.Frame):
         c.config(scrollregion = (0, 0, cw - 2, ch - 2))
         #print("Place c %d %d, p %d %d" % (cw, ch, w, h))
         c.create_image(cw // 2, ch // 2, image = self.canv_image)
-       
+
     def make_image(self, imgobj):
         if imgobj.image is not None:
             return imgobj.image
@@ -456,13 +456,13 @@ class TkBrowser(tkinter.Frame):
             if ch > 0x7f:
                 p += bytes((0b11000000 |\
                     ch >> 6, 0b10000000 |\
-                    (ch & 0b00111111)))               
+                    (ch & 0b00111111)))
             else:
                 p += bytes((ch,))
         image = tkinter.PhotoImage(width = width, height = height, \
             data = bytes(p))
-        return image                
-   
+        return image
+
     def update_gui(self, text = "<Undefined>"):
         self.last_path = self.curr_path
         # cleanup
@@ -518,7 +518,7 @@ class TkBrowser(tkinter.Frame):
             )
             self.scr_view_x.config(command = self.text_view.xview)
             self.scr_view_y.config(command = self.text_view.yview)
-        else:        
+        else:
             if last == 0:
                 rw = self.text_view.winfo_width()
                 rh = self.text_view.winfo_height()
@@ -546,7 +546,7 @@ class TkBrowser(tkinter.Frame):
 
     def add_info(self, text):
         self.curr_markup += text
-        
+
     def end_markup(self):
         if not self.curr_markup: return
         def make_cb(path):
@@ -557,7 +557,7 @@ class TkBrowser(tkinter.Frame):
             return cb
         self.text_hl.add_markup(self.curr_markup, self.text_view, make_cb)
         self.curr_markup = ""
-        
+
     def insert_lb_act(self, name, act, key = None):
         if key is not None:
             self.curr_lb_idx[key] = len(self.curr_lb_acts)
@@ -580,7 +580,7 @@ class TkBrowser(tkinter.Frame):
             self.curr_lb.selection_set(idxs)
         if idx is not None:
             self.curr_lb.see(idxs)
-            
+
     def on_left_listbox(self, event):
         def currsel():
             try:
@@ -599,14 +599,14 @@ class TkBrowser(tkinter.Frame):
         if text is None:
             frm = ttk.Frame(self.toolbar, width = self.pad, height = self.pad)
             frm.pack(side = tkinter.LEFT)
-            self.curr_gui.append(lambda:frm.pack_forget())    
+            self.curr_gui.append(lambda:frm.pack_forget())
             return
         btn = ttk.Button(self.toolbar, text = text, \
             style = "Tool.TButton", command = cmd)
         btn.pack(side = tkinter.LEFT)
         self.curr_gui.append(lambda:btn.pack_forget())
         return btn
-        
+
     def add_toollabel(self, text):
         lab = ttk.Label(self.toolbar, text = text)
         lab.pack(side = tkinter.LEFT)
@@ -635,16 +635,16 @@ class TkBrowser(tkinter.Frame):
                 btn.config(state = tkinter.NORMAL)
             else:
                 btn.config(state = tkinter.DISABLED)
-    
+
     def clear_hist(self):
         self.hist = self.hist[-1:]
         self.histf = []
 
     def open_http(self, path):
         messagebox.showinfo(parent = self, title = "URL", message = path)
- 
+
     def open_path(self, loc, withhist = True):
-        path = self.parse_path(loc)        
+        path = self.parse_path(loc)
         if withhist:
             self.hist.append([path])
             self.histf = []
@@ -671,5 +671,3 @@ class TkBrowser(tkinter.Frame):
         self.clear_info()
         self.add_info("Open path\n\n" + str(path))
         return True
-        
-




More information about the Scummvm-git-logs mailing list