diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java index 340cf7bf8..b4ebe349b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java @@ -325,6 +325,14 @@ public class Helper { return ret; } + public static String byteArrayToHex(byte[] a) { + StringBuilder sb = new StringBuilder(a.length * 2); + for (byte b : a) { + sb.append(String.format("%02x", b & 0xff)); + } + return sb.toString(); + } + public static String bytesToHexString(byte[] bytes) { return bytesToHexString(bytes, 0); } diff --git a/libsrc/ffdec_lib/testdata/binary.bin b/libsrc/ffdec_lib/testdata/binary.bin new file mode 100644 index 000000000..daf1f4a14 Binary files /dev/null and b/libsrc/ffdec_lib/testdata/binary.bin differ diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index ba7e86f71..ee988b852 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -23,21 +23,12 @@ import com.jpexs.decompiler.flash.SWFBundle; import com.jpexs.decompiler.flash.SWFSourceInfo; import com.jpexs.decompiler.flash.SearchMode; import com.jpexs.decompiler.flash.Version; -import com.jpexs.decompiler.flash.abc.ABC; -import com.jpexs.decompiler.flash.abc.ClassPath; -import com.jpexs.decompiler.flash.abc.ScriptPack; import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; -import com.jpexs.decompiler.flash.abc.types.Multiname; -import com.jpexs.decompiler.flash.abc.types.Namespace; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.console.CommandLineArgumentParser; import com.jpexs.decompiler.flash.console.ContextMenuTools; -import com.jpexs.decompiler.flash.gui.debugger.Debugger; import com.jpexs.decompiler.flash.gui.proxy.ProxyFrame; import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin; -import com.jpexs.decompiler.flash.helpers.collections.MyEntry; -import com.jpexs.decompiler.flash.tags.ABCContainerTag; -import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.base.FontTag; import com.jpexs.decompiler.flash.treeitems.SWFList; import com.jpexs.helpers.Cache; @@ -84,7 +75,6 @@ import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map.Entry; -import java.util.Random; import java.util.logging.ConsoleHandler; import java.util.logging.FileHandler; import java.util.logging.Formatter; @@ -114,8 +104,6 @@ public class Main { public static LoadingDialog loadingDialog; - public static ModeFrame modeFrame; - private static boolean working = false; private static TrayIcon trayIcon; @@ -134,44 +122,10 @@ public class Main { private static final Logger logger = Logger.getLogger(Main.class.getName()); - private static Debugger debugger; - public static DebugLogDialog debugDialog; public static boolean shouldCloseWhenClosingLoadingDialog; - public static final String DEBUGGER_PACKAGE = "com.jpexs.decompiler.flash.debugger"; - - private static ABCContainerTag getDebuggerABCTag(SWF swf) { - for (ABCContainerTag ac : swf.getAbcList()) { - ABC a = ac.getABC(); - for (MyEntry m : a.getScriptPacks()) { - if (isDebuggerClass(m.getKey().packageStr, null)) { - return ac; - } - } - } - return null; - } - - private static String getDebuggerPackage(SWF swf) { - ABCContainerTag ac = getDebuggerABCTag(swf); - if (ac == null) { - return null; - } - ABC a = ac.getABC(); - for (MyEntry m : a.getScriptPacks()) { - if (isDebuggerClass(m.getKey().packageStr, null)) { - return m.getKey().packageStr; - } - } - return null; - } - - public static boolean hasDebugger(SWF swf) { - return getDebuggerABCTag(swf) != null; - } - public static void ensureMainFrame() { if (mainFrame == null) { synchronized (Main.class) { @@ -1184,15 +1138,6 @@ public class Main { public static void exit() { Configuration.saveConfig(); - /*if (proxyFrame != null && Main.proxyFrame.isVisible()) { - return; - } - if (loadFromMemoryFrame != null && Main.loadFromMemoryFrame.isVisible()) { - return; - } - if (loadFromCacheFrame != null && loadFromCacheFrame.isVisible()) { - return; - }*/ if (mainFrame != null && mainFrame.getPanel() != null) { mainFrame.getPanel().unloadFlashPlayer(); } @@ -1207,135 +1152,6 @@ public class Main { (new AdvancedSettingsDialog()).setVisible(true); } - private static boolean isDebuggerClass(String tested, String cls) { - if (tested == null) { - return false; - } - if (cls == null) { - cls = ""; - } else { - cls = "\\." + Pattern.quote(cls); - } - return tested.matches(Pattern.quote(DEBUGGER_PACKAGE) + "(\\.pkg[a-f0-9]+)?" + cls); - } - - private static String byteArrayToHex(byte[] a) { - StringBuilder sb = new StringBuilder(a.length * 2); - for (byte b : a) { - sb.append(String.format("%02x", b & 0xff)); - } - return sb.toString(); - } - - public static void replaceTraceCalls(String fname) { - SWF swf = getMainFrame().getPanel().getCurrentSwf(); - if (hasDebugger(swf)) { - String debuggerPkg = getDebuggerPackage(swf); - //change trace to fname - for (ABCContainerTag ct : swf.getAbcList()) { - ABC a = ct.getABC(); - for (int i = 1; i < a.constants.constant_multiname.size(); i++) { - Multiname m = a.constants.constant_multiname.get(i); - if ("trace".equals(m.getNameWithNamespace(a.constants, true))) { - m.namespace_index = a.constants.getNamespaceId(new Namespace(Namespace.KIND_PACKAGE, a.constants.getStringId(debuggerPkg, true)), 0, true); - m.name_index = a.constants.getStringId(fname, true); - ((Tag) ct).setModified(true); - } - } - } - } - } - - public static void switchDebugger() { - int port = Configuration.debuggerPort.get(); - SWF swf = getMainFrame().getPanel().getCurrentSwf(); - ABCContainerTag found = getDebuggerABCTag(swf); - if (found != null) { - swf.tags.remove((Tag) found); - swf.getAbcList().remove(found); - - //Change all debugger calls to normal trace - for (ABCContainerTag ct : swf.getAbcList()) { - ABC a = ct.getABC(); - for (int i = 1; i < a.constants.constant_multiname.size(); i++) { - Multiname m = a.constants.constant_multiname.get(i); - if (isDebuggerClass(m.getNameWithNamespace(a.constants, true), "debugTrace") - || isDebuggerClass(m.getNameWithNamespace(a.constants, true), "debugAlert") - || isDebuggerClass(m.getNameWithNamespace(a.constants, true), "debugSocket") - || isDebuggerClass(m.getNameWithNamespace(a.constants, true), "debugConsole")) { - m.name_index = a.constants.getStringId("trace", true); - m.namespace_index = a.constants.getNamespaceId(new Namespace(Namespace.KIND_PACKAGE, a.constants.getStringId("", true)), 0, true); - ((Tag) ct).setModified(true); - } - } - } - } else { - Random rnd = new Random(); - byte rb[] = new byte[16]; - rnd.nextBytes(rb); - String rhex = byteArrayToHex(rb); - try { - //load debug swf - SWF debugSWF = new SWF(Main.class.getClassLoader().getResourceAsStream("com/jpexs/decompiler/flash/gui/debugger/debug.swf"), false); - - ABCContainerTag firstAbc = swf.getAbcList().get(0); - String newdebuggerpkg = DEBUGGER_PACKAGE; - - if (Configuration.randomDebuggerPackage.get()) { - newdebuggerpkg += ".pkg" + rhex; - } - - //add debug ABC tags to main SWF - for (ABCContainerTag ds : debugSWF.getAbcList()) { - ABC a = ds.getABC(); - //Append random hex to Debugger package name - for (int i = 1; i < a.constants.constant_namespace.size(); i++) { - if (a.constants.constant_namespace.get(i).hasName(DEBUGGER_PACKAGE, a.constants)) { - a.constants.constant_namespace.get(i).name_index = a.constants.getStringId(newdebuggerpkg, true); - } - } - //Set debugger port to actually set port - for (int i = 0; i < a.constants.constant_int.size(); i++) { - if (a.constants.constant_int.get(i) == 123456L) { - a.constants.constant_int.set(i, (long) port); - } - } - //Add to target SWF - ((Tag) ds).setSwf(swf); - swf.tags.add(swf.tags.indexOf(firstAbc), (Tag) ds); - swf.getAbcList().add(swf.getAbcList().indexOf(firstAbc), ds); - ((Tag) ds).setModified(true); - } - - } catch (Exception ex) { - logger.log(Level.SEVERE, "Error while attaching debugger", ex); - //ignore - } - - } - initDebugger(); - } - - private static void initDebugger() { - if (debugger == null) { - synchronized (Main.class) { - if (debugger == null) { - Debugger dbg = new Debugger(Configuration.debuggerPort.get()); - dbg.start(); - debugger = dbg; - } - } - } - } - - public static void debuggerShowLog() { - initDebugger(); - if (debugDialog == null) { - debugDialog = new DebugLogDialog(debugger); - } - debugDialog.setVisible(true); - } - public static void autoCheckForUpdates() { if (Configuration.checkForUpdatesAuto.get()) { Calendar lastUpdatesCheckDate = Configuration.lastUpdatesCheckDate.get(); diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java index 34783d777..9eafac4cf 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java @@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.ApplicationInfo; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFBundle; import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.gui.debugger.DebuggerTools; import com.jpexs.decompiler.flash.gui.helpers.CheckResources; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.utf8.Utf8Helper; @@ -30,6 +31,7 @@ import java.awt.KeyEventDispatcher; import java.awt.KeyboardFocusManager; import java.awt.ScrollPane; import java.awt.event.KeyEvent; +import java.awt.event.WindowEvent; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -140,7 +142,11 @@ public abstract class MainFrameMenu { } protected void close() { - Main.closeFile(mainFrame.getPanel().getCurrentSwfList()); + if (swf == null) { + return; + } + + Main.closeFile(swf.swfList); } protected boolean closeAll() { @@ -216,6 +222,18 @@ public abstract class MainFrameMenu { Main.setSubLimiter(value); } + protected void switchDebugger() { + DebuggerTools.switchDebugger(swf); + } + + protected void debuggerShowLog() { + DebuggerTools.debuggerShowLog(); + } + + protected void replaceTraceCalls(String fname) { + DebuggerTools.replaceTraceCalls(swf, fname); + } + protected void removeNonScripts() { mainFrame.getPanel().removeNonScripts(swf); } @@ -311,13 +329,8 @@ public abstract class MainFrameMenu { } protected void exit() { - mainFrame.getPanel().setVisible(false); - if (Main.proxyFrame != null) { - if (Main.proxyFrame.isVisible()) { - return; - } - } - Main.exit(); + JFrame frame = (JFrame) mainFrame; + frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING)); } public void updateComponents(SWF swf) { @@ -331,7 +344,7 @@ public abstract class MainFrameMenu { @Override public boolean dispatchKeyEvent(KeyEvent e) { - if (((JFrame) mainFrame).isActive() && e.getID() == KeyEvent.KEY_RELEASED) { + if (((JFrame) mainFrame).isActive() && e.getID() == KeyEvent.KEY_PRESSED) { int code = e.getKeyCode(); if (e.isControlDown() && e.isShiftDown()) { switch (code) { diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java index 670aa3331..f203d41f4 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java @@ -19,6 +19,7 @@ package com.jpexs.decompiler.flash.gui; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.console.ContextMenuTools; +import com.jpexs.decompiler.flash.gui.debugger.DebuggerTools; import com.jpexs.decompiler.flash.tags.ABCContainerTag; import com.jpexs.helpers.Cache; import com.jpexs.process.ProcessTools; @@ -718,7 +719,7 @@ public class MainFrameRibbonMenu extends MainFrameMenu implements ActionListener boolean swfLoaded = swf != null; List abcList = swfLoaded ? swf.getAbcList() : null; boolean hasAbc = swfLoaded && abcList != null && !abcList.isEmpty(); - boolean hasDebugger = hasAbc && Main.hasDebugger(swf); + boolean hasDebugger = hasAbc && DebuggerTools.hasDebugger(swf); exportAllMenu.setEnabled(swfLoaded); exportFlaMenu.setEnabled(swfLoaded); @@ -762,7 +763,7 @@ public class MainFrameRibbonMenu extends MainFrameMenu implements ActionListener switch (e.getActionCommand()) { case ACTION_DEBUGGER_SWITCH: if (debuggerSwitchGroup.getSelected() == null || View.showConfirmDialog(mainFrame, translate("message.debugger"), translate("dialog.message.title"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE, Configuration.displayDebuggerInfo, JOptionPane.OK_OPTION) == JOptionPane.OK_OPTION) { - Main.switchDebugger(); + switchDebugger(); mainFrame.getPanel().refreshDecompiled(); } else { if (debuggerSwitchGroup.getSelected() == debuggerSwitchCommandButton) { @@ -772,13 +773,13 @@ public class MainFrameRibbonMenu extends MainFrameMenu implements ActionListener debuggerReplaceTraceCommandButton.setEnabled(debuggerSwitchGroup.getSelected() == debuggerSwitchCommandButton); break; case ACTION_DEBUGGER_LOG: - Main.debuggerShowLog(); + debuggerShowLog(); break; case ACTION_DEBUGGER_REPLACE_TRACE: ReplaceTraceDialog rtd = new ReplaceTraceDialog(mainFrame, Configuration.lastDebuggerReplaceFunction.get()); rtd.setVisible(true); if (rtd.getValue() != null) { - Main.replaceTraceCalls(rtd.getValue()); + replaceTraceCalls(rtd.getValue()); mainFrame.getPanel().refreshDecompiled(); Configuration.lastDebuggerReplaceFunction.set(rtd.getValue()); } diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 81d1f6033..e5f16f843 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -2081,6 +2081,7 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec int row = tagTree.getSelectionRows()[0]; if (row > 0) { tagTree.setSelectionRow(row - 1); + tagTree.scrollRowToVisible(row - 1); previewPanel.focusTextPanel(); } } @@ -2095,6 +2096,7 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec int row = tagTree.getSelectionRows()[0]; if (row < tagTree.getRowCount() - 1) { tagTree.setSelectionRow(row + 1); + tagTree.scrollRowToVisible(row + 1); previewPanel.focusTextPanel(); } } diff --git a/src/com/jpexs/decompiler/flash/gui/debugger/DebuggerTools.java b/src/com/jpexs/decompiler/flash/gui/debugger/DebuggerTools.java new file mode 100644 index 000000000..4b79cf335 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/debugger/DebuggerTools.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2010-2015 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.debugger; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.ClassPath; +import com.jpexs.decompiler.flash.abc.ScriptPack; +import com.jpexs.decompiler.flash.abc.types.Multiname; +import com.jpexs.decompiler.flash.abc.types.Namespace; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.gui.DebugLogDialog; +import com.jpexs.decompiler.flash.gui.Main; +import com.jpexs.decompiler.flash.helpers.collections.MyEntry; +import com.jpexs.decompiler.flash.tags.ABCContainerTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.helpers.Helper; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/** + * + * @author JPEXS + */ +public class DebuggerTools { + + private static final Logger logger = Logger.getLogger(DebuggerTools.class.getName()); + + public static final String DEBUGGER_PACKAGE = "com.jpexs.decompiler.flash.debugger"; + + private static Debugger debugger; + + private static ScriptPack getDebuggerScriptPack(SWF swf) { + for (ABCContainerTag ac : swf.getAbcList()) { + ABC a = ac.getABC(); + for (MyEntry m : a.getScriptPacks()) { + if (isDebuggerClass(m.getKey().packageStr, null)) { + return m.getValue(); + } + } + } + return null; + } + + public static boolean hasDebugger(SWF swf) { + return getDebuggerScriptPack(swf) != null; + } + + private static boolean isDebuggerClass(String tested, String cls) { + if (tested == null) { + return false; + } + if (cls == null) { + cls = ""; + } else { + cls = "\\." + Pattern.quote(cls); + } + return tested.matches(Pattern.quote(DEBUGGER_PACKAGE) + "(\\.pkg[a-f0-9]+)?" + cls); + } + + public static void replaceTraceCalls(SWF swf, String fname) { + if (hasDebugger(swf)) { + String debuggerPkg = getDebuggerScriptPack(swf).getClassPath().packageStr; + //change trace to fname + for (ABCContainerTag ct : swf.getAbcList()) { + ABC a = ct.getABC(); + for (int i = 1; i < a.constants.constant_multiname.size(); i++) { + Multiname m = a.constants.constant_multiname.get(i); + if ("trace".equals(m.getNameWithNamespace(a.constants, true))) { + m.namespace_index = a.constants.getNamespaceId(new Namespace(Namespace.KIND_PACKAGE, a.constants.getStringId(debuggerPkg, true)), 0, true); + m.name_index = a.constants.getStringId(fname, true); + ((Tag) ct).setModified(true); + } + } + } + } + } + + public static void switchDebugger(SWF swf) { + int port = Configuration.debuggerPort.get(); + ScriptPack found = getDebuggerScriptPack(swf); + if (found != null) { + ABCContainerTag tag = found.abc.parentTag; + swf.tags.remove((Tag) tag); + swf.getAbcList().remove(tag); + + //Change all debugger calls to normal trace + for (ABCContainerTag ct : swf.getAbcList()) { + ABC a = ct.getABC(); + for (int i = 1; i < a.constants.constant_multiname.size(); i++) { + Multiname m = a.constants.constant_multiname.get(i); + String packageStr = m.getNameWithNamespace(a.constants, true); + if (isDebuggerClass(packageStr, "debugTrace") + || isDebuggerClass(packageStr, "debugAlert") + || isDebuggerClass(packageStr, "debugSocket") + || isDebuggerClass(packageStr, "debugConsole")) { + m.name_index = a.constants.getStringId("trace", true); + m.namespace_index = a.constants.getNamespaceId(new Namespace(Namespace.KIND_PACKAGE, a.constants.getStringId("", true)), 0, true); + ((Tag) ct).setModified(true); + } + } + } + } else { + Random rnd = new Random(); + byte rb[] = new byte[16]; + rnd.nextBytes(rb); + String rhex = Helper.byteArrayToHex(rb); + try { + //load debug swf + SWF debugSWF = new SWF(Main.class.getClassLoader().getResourceAsStream("com/jpexs/decompiler/flash/gui/debugger/debug.swf"), false); + + ABCContainerTag firstAbc = swf.getAbcList().get(0); + String newdebuggerpkg = DEBUGGER_PACKAGE; + + if (Configuration.randomDebuggerPackage.get()) { + newdebuggerpkg += ".pkg" + rhex; + } + + //add debug ABC tags to main SWF + for (ABCContainerTag ds : debugSWF.getAbcList()) { + ABC a = ds.getABC(); + //Append random hex to Debugger package name + for (int i = 1; i < a.constants.constant_namespace.size(); i++) { + if (a.constants.constant_namespace.get(i).hasName(DEBUGGER_PACKAGE, a.constants)) { + a.constants.constant_namespace.get(i).name_index = a.constants.getStringId(newdebuggerpkg, true); + } + } + //Set debugger port to actually set port + for (int i = 0; i < a.constants.constant_int.size(); i++) { + if (a.constants.constant_int.get(i) == 123456L) { + a.constants.constant_int.set(i, (long) port); + } + } + //Add to target SWF + ((Tag) ds).setSwf(swf); + swf.tags.add(swf.tags.indexOf(firstAbc), (Tag) ds); + swf.getAbcList().add(swf.getAbcList().indexOf(firstAbc), ds); + ((Tag) ds).setModified(true); + } + + } catch (Exception ex) { + logger.log(Level.SEVERE, "Error while attaching debugger", ex); + //ignore + } + + } + initDebugger(); + } + + private static void initDebugger() { + if (debugger == null) { + synchronized (Main.class) { + if (debugger == null) { + Debugger dbg = new Debugger(Configuration.debuggerPort.get()); + dbg.start(); + debugger = dbg; + } + } + } + } + + public static void debuggerShowLog() { + initDebugger(); + if (Main.debugDialog == null) { + Main.debugDialog = new DebugLogDialog(debugger); + } + Main.debugDialog.setVisible(true); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_ru.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_ru.properties index e836308a6..da956a0f7 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_ru.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_ru.properties @@ -572,3 +572,5 @@ menu.file.export.xml = \u042d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u0 #after version 4.1.1 text.align.translatex.decrease = \u0423\u043c\u0435\u043d\u044c\u0448\u0438\u0442\u044c TranslateX text.align.translatex.increase = \u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0442\u044c TranslateX +selectPreviousTag = \u0412\u044b\u0431\u0440\u0430\u0442\u044c \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0439 \u0442\u044d\u0433 +selectNextTag = \u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0442\u044d\u0433