├── docs ├── source │ ├── _templates │ │ ├── module.html │ │ ├── autosummary │ │ │ └── module.rst │ │ └── layout.html │ ├── CLI │ │ ├── wiki.rst │ │ ├── setup.rst │ │ ├── overview.rst │ │ ├── dat.rst │ │ └── config.rst │ ├── API │ │ ├── overview.rst │ │ └── structure.rst │ ├── GUI │ │ ├── overview.rst │ │ └── ggpk_viewer.rst │ ├── report_issue.rst │ ├── index.rst │ └── contribution.rst └── generate_rst_templates.py ├── setup.cfg ├── .gitattributes ├── PyPoE ├── _data │ └── custom_descriptions.txt ├── cli │ ├── __init__.py │ └── exporter │ │ ├── wiki │ │ ├── admin │ │ │ └── __init__.py │ │ ├── __init__.py │ │ ├── parsers │ │ │ └── __init__.py │ │ └── core.py │ │ ├── dat │ │ ├── parsers │ │ │ └── __init__.py │ │ ├── __init__.py │ │ └── handler.py │ │ ├── __init__.py │ │ ├── util.py │ │ └── core.py ├── poe │ ├── file │ │ ├── specification │ │ │ ├── data │ │ │ │ └── __init__.py │ │ │ ├── __init__.py │ │ │ └── errors.py │ │ ├── __init__.py │ │ ├── ot.py │ │ └── stat_filters.py │ ├── sim │ │ └── __init__.py │ └── __init__.py ├── shared │ ├── config │ │ └── __init__.py │ ├── __init__.py │ ├── murmur2.py │ └── containers.py ├── ui │ ├── __init__.py │ ├── shared │ │ ├── file │ │ │ └── __init__.py │ │ ├── table_context_menus.py │ │ ├── __init__.py │ │ └── dialog.py │ ├── ggpk_viewer │ │ └── __init__.py │ └── launchpad │ │ └── __init__.py └── __init__.py ├── tests ├── PyPoE │ ├── poe │ │ ├── file │ │ │ ├── _data │ │ │ │ ├── test.idl │ │ │ │ ├── test.idt │ │ │ │ ├── keyvalues.kv │ │ │ │ ├── test_write.idl │ │ │ │ ├── keyvalues_base.kv │ │ │ │ ├── keyvalues_write.kv │ │ │ │ ├── Metadata │ │ │ │ │ └── StatDescriptions │ │ │ │ │ │ ├── descriptions_base.txt │ │ │ │ │ │ └── descriptions_extended.txt │ │ │ │ └── specifications │ │ │ │ │ ├── runtime_rowsize_mismatch.py │ │ │ │ │ ├── invalid_enum_name.py │ │ │ │ │ ├── invalid_foreign_key_file.py │ │ │ │ │ ├── invalid_foreign_key_id.py │ │ │ │ │ ├── invalid_argument_combination.py │ │ │ │ │ ├── virtual_key_empty.py │ │ │ │ │ ├── virtual_key_duplicate.py │ │ │ │ │ ├── virtual_key_invalid_key.py │ │ │ │ │ ├── virtual_key_invalid_data_type.py │ │ │ │ │ ├── runtime_missing_foreign_key1.py │ │ │ │ │ ├── runtime_missing_foreign_key2.py │ │ │ │ │ ├── rr_test.py │ │ │ │ │ └── dat_testspec.py │ │ │ ├── test_ot.py │ │ │ ├── test_psg.py │ │ │ ├── test_stat_filters.py │ │ │ ├── test_ggpk.py │ │ │ ├── test_idt.py │ │ │ └── test_idl.py │ │ ├── test_path.py │ │ └── test_text.py │ └── shared │ │ ├── test_murmur2.py │ │ ├── test_mixins.py │ │ └── config │ │ └── test_validator.py └── test_data.py ├── .gitignore ├── test_requirements.txt ├── .travis.yml ├── LICENSE ├── scripts ├── profile │ └── PyPoE │ │ ├── poe │ │ └── file │ │ │ ├── translations.py │ │ │ ├── ot.py │ │ │ └── dat.py │ │ └── ui │ │ └── dat_handler.py └── make_empty_spec.py ├── README.md └── setup.py /docs/source/_templates/module.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [options] 2 | python_requires = >= 3.6 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.py ident 2 | *.ini ident 3 | *.txt ident -------------------------------------------------------------------------------- /PyPoE/_data/custom_descriptions.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmegaK2/PyPoE/HEAD/PyPoE/_data/custom_descriptions.txt -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/test.idl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmegaK2/PyPoE/HEAD/tests/PyPoE/poe/file/_data/test.idl -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/test.idt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmegaK2/PyPoE/HEAD/tests/PyPoE/poe/file/_data/test.idt -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/keyvalues.kv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmegaK2/PyPoE/HEAD/tests/PyPoE/poe/file/_data/keyvalues.kv -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/test_write.idl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmegaK2/PyPoE/HEAD/tests/PyPoE/poe/file/_data/test_write.idl -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/keyvalues_base.kv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmegaK2/PyPoE/HEAD/tests/PyPoE/poe/file/_data/keyvalues_base.kv -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/keyvalues_write.kv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmegaK2/PyPoE/HEAD/tests/PyPoE/poe/file/_data/keyvalues_write.kv -------------------------------------------------------------------------------- /docs/source/_templates/autosummary/module.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: {{fullname}} 2 | :no-members: 3 | :no-undoc-members: 4 | :no-special-members: 5 | :no-inherited-members: -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | .idea 3 | .svn 4 | .pyc 5 | .cache 6 | __pycache__ 7 | /PyPoE.egg-info 8 | /build 9 | /docs/build 10 | /docs/source/_autosummary 11 | /docs/source/autosummary.rst -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/Metadata/StatDescriptions/descriptions_base.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmegaK2/PyPoE/HEAD/tests/PyPoE/poe/file/_data/Metadata/StatDescriptions/descriptions_base.txt -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/Metadata/StatDescriptions/descriptions_extended.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmegaK2/PyPoE/HEAD/tests/PyPoE/poe/file/_data/Metadata/StatDescriptions/descriptions_extended.txt -------------------------------------------------------------------------------- /test_requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | sphinx 3 | colorama 4 | graphviz 5 | tqdm 6 | mwclient 7 | mwparserfromhell 8 | rapidfuzz 9 | sqlalchemy 10 | pymysql 11 | PyOpenGL 12 | pytest-cov 13 | python-coveralls 14 | cffi 15 | fnvhash -------------------------------------------------------------------------------- /docs/source/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | 3 | {% block header %} 4 | {% if fullname is defined %} 5 | {% set parent_name = fullname.rsplit('.', 1)[0] %} 6 | {% if parent_name %} 7 | {{parent_name}} 8 | {% endif %} 9 | {% endif %} 10 | 11 | {% endblock %} -------------------------------------------------------------------------------- /docs/source/CLI/wiki.rst: -------------------------------------------------------------------------------- 1 | Wiki Exporter 2 | ============================================================================== 3 | 4 | Setup sub commands are accessible with: 5 | 6 | :command:`wiki ` 7 | 8 | Commands 9 | ------------------------------------------------------------------------------ 10 | 11 | wiki 12 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: bionic 3 | language: python 4 | cache: 5 | - pip 6 | - apt 7 | python: 8 | - "3.7" 9 | before_install: 10 | - sudo apt-get -qq update 11 | # for PySide/QT4 12 | - sudo apt-get install -y python3-pyside 13 | install: 14 | - "pip install -U pip setuptools" 15 | - "pip install -U -r test_requirements.txt" 16 | - "pip install -U -e ." 17 | script: 18 | - "py.test --cov=PyPoE" 19 | after_success: 20 | - coveralls 21 | -------------------------------------------------------------------------------- /docs/source/API/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | ============================================================================== 3 | 4 | The API of PyPoE provides many classes and functions to access the proprietary 5 | file formats in an attempt to recreate and open source a lot of Path of Exile 6 | functionality for generic purpose tool programming. 7 | 8 | It is a good idea to take a look at the :ref:`modindex` to get started - the 9 | relevant modules are located under the :mod:`PyPoE.poe` tree. -------------------------------------------------------------------------------- /docs/source/CLI/setup.rst: -------------------------------------------------------------------------------- 1 | Setup 2 | ============================================================================== 3 | 4 | Setup sub commands are accessible with: 5 | 6 | :command:`setup ` 7 | 8 | Commands 9 | ------------------------------------------------------------------------------ 10 | 11 | .. _setup-perform: 12 | 13 | setup perform 14 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 15 | :command:`setup peform` is used to perform the setup 16 | 17 | Depending on the type of setup and the speed of your computer this action may 18 | take a while. -------------------------------------------------------------------------------- /docs/source/GUI/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | ============================================================================== 3 | 4 | The graphical user interface of PyPoE features various utilities split up into 5 | several applications. 6 | 7 | Those applications can be accessed though the launchpad, which can be run 8 | with: 9 | 10 | :command:`pypoe_ui` 11 | 12 | 13 | The individual applications (except the launchpad) can also be directly invoked 14 | through the command line by executing the top-level __init__.py file: 15 | 16 | :command:`python /PyPoE/ui//__init__.py` 17 | 18 | So for example: 19 | 20 | :command:`python /home/myuser/PyPoE/ui/ggpk_viewer/__init__.py` -------------------------------------------------------------------------------- /docs/source/CLI/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | ============================================================================== 3 | 4 | The command line interface of PyPoE features various utilities split up into 5 | command line scripts. 6 | 7 | Currently there are the following scripts: 8 | 9 | :command:`pypoe_exporter` 10 | Exporting functionality for the Path of Exile wiki and .dat files 11 | 12 | 13 | Invoking a script without any additional parameters will implicitly tell you 14 | about the available sub commands and command line options. 15 | However, it is possible to explicitly open the help by specifying :command:`-h` 16 | or :command:`--help`. 17 | 18 | So for example: 19 | 20 | :command:`pypoe_exporter -h` 21 | 22 | 23 | Each of the scripts comes with their own :ref:`CLI/config`. In order to use the 24 | scripts the config variables need to be set first, then the setup needs to be 25 | performed. -------------------------------------------------------------------------------- /docs/source/report_issue.rst: -------------------------------------------------------------------------------- 1 | Reporting an issue 2 | =============================================================================== 3 | 4 | First please make sure whether is unknown/not reported yet: 5 | 6 | * Validate it is an issue on PyPoE's end and not just a coding mistake on your 7 | end 8 | * Make sure you are running the latest version of PyPoE 9 | * Check open github issues: https://github.com/OmegaK2/PyPoE/issues 10 | * Check the "TODO"s in the affected file or function 11 | 12 | After taking those steps please report the issue to github and provide as 13 | much information as you can: 14 | 15 | * Title: Short, but definitive description of the bug 16 | * Description: Detailed description of the bug, in particular steps on how to 17 | reproduce it 18 | * if available, provide the relevant python traceback 19 | * if available, provide the relevant code that caused the bug 20 | 21 | You can also submit pull requests that to help fix bugs, I'll review (and 22 | possibly) edit them. You agree they'll placed under the MIT license (for more 23 | info see :doc:`contribution`) -------------------------------------------------------------------------------- /PyPoE/cli/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | CLI Package Init 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/cli/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: c35ec0457b6736d7b2b1fa6d3d4d1f359b868646 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | CLI Package init 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | -------------------------------------------------------------------------------- /docs/source/CLI/dat.rst: -------------------------------------------------------------------------------- 1 | Dat Exporter 2 | ============================================================================== 3 | 4 | Setup sub commands are accessible with: 5 | 6 | :command:`dat ` 7 | 8 | Commands 9 | ------------------------------------------------------------------------------ 10 | 11 | dat json 12 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 13 | 14 | :command:`dat json [--files FILE [FILE ...]]` 15 | 16 | The dat json export is used to export .dat files to json format. 17 | 18 | The resulting json will roughly look like this: 19 | 20 | .. code-block:: none 21 | 22 | [ 23 | { 24 | // One of the files you've specified in the --files parameter 25 | "filename": "FILE", 26 | // headers in order from the dat specification 27 | "headers": [ 28 | { 29 | // name of the header 30 | 'name': 'Name', 31 | [ ... ] // other fields 32 | }, 33 | ... // other headers 34 | ] 35 | "data": [ 36 | // Each row 37 | [ ... ], 38 | ], 39 | }, 40 | ... // other files 41 | ] 42 | 43 | For details on what the extra fields mean in the headers, please see 44 | the dat specification file: 45 | https://github.com/OmegaK2/PyPoE/blob/dev/PyPoE/_data/dat.specification.ini -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 OmegaK2 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | ------------------------------------------------------------------------------ 24 | 25 | Additionally please note that many of the file formats, and in particular 26 | the contents of extracted files are owned by Grinding Gear Games and shall 27 | not be used or published without being in accordance with their terms of use. 28 | 29 | -------------------------------------------------------------------------------- /docs/source/GUI/ggpk_viewer.rst: -------------------------------------------------------------------------------- 1 | GGPK Viewer 2 | =============================================================================== 3 | 4 | The GGPK Viewer is as the name suggests an utility to browse the contents of 5 | Path of Exile's content.ggpk. 6 | 7 | Features in a nutshell: 8 | 9 | * browsing the ggpk content file tree 10 | * searching for files using regular expressions (see :py:mod:`re`) 11 | * extracting of files or directories 12 | * live opening of various file formats, but in particular the proprietary .dat 13 | file format 14 | 15 | .. note:: 16 | Opening the GGPK may take several seconds depending on your disk and cpu 17 | speed. 18 | 19 | Viewing .dat files 20 | ------------------------------------------------------------------------------- 21 | 22 | The GGPK Viewer includes the ability to view the binary .dat files. 23 | 24 | They're parsed in a way that provides extra information about the entries 25 | for viewing purposes and in particular updating PyPoE's .dat specification. 26 | 27 | In particular information about pointers is kept intact and parsed into a table 28 | format adjusted , as such the dat viewer can be much slower then the API 29 | counter-part (i.e :class:`DatFile`). 30 | 31 | .. warning:: 32 | Opening large .dat files such as GrantedEffectsPerLevel.dat will 'hang' 33 | the UI until the processing is completed. 34 | 35 | As soon a .dat file is opening, filters can be applied to the individual 36 | rows by right-clicking the header columns. 37 | 38 | Columns can also be sorted by left-clicking on them. -------------------------------------------------------------------------------- /PyPoE/poe/file/specification/data/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | =============================================================================== 4 | 5 | +----------+------------------------------------------------------------------+ 6 | | Path | PyPoE/poe/file/specification/data/__init__.py | 7 | +----------+------------------------------------------------------------------+ 8 | | Version | 1.0.0a0 | 9 | +----------+------------------------------------------------------------------+ 10 | | Revision | $Id: d891d7c24650b544fe0b352fe540d1e26de8660d $ | 11 | +----------+------------------------------------------------------------------+ 12 | | Author | Omega_K2 | 13 | +----------+------------------------------------------------------------------+ 14 | 15 | Description 16 | =============================================================================== 17 | 18 | .dat specifications for the GGG Path of Exile client 19 | 20 | Agreement 21 | =============================================================================== 22 | 23 | See PyPoE/LICENSE 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | 32 | # 3rd-party 33 | 34 | # self 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | __all__ = [] 41 | -------------------------------------------------------------------------------- /PyPoE/cli/exporter/wiki/admin/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Administrative parser init 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/cli/exporter/wiki/admin/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 837f95cc8c9994cb0b217c4ea34a3673ba302322 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Agreement 21 | =============================================================================== 22 | 23 | See PyPoE/LICENSE 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | from PyPoE.cli.exporter.wiki.admin.unique import UniqueCommandHandler 31 | 32 | # ============================================================================= 33 | # Globals 34 | # ============================================================================= 35 | 36 | 37 | ADMIN_HANDLERS = [UniqueCommandHandler] 38 | 39 | __all__ = ['ADMIN_HANDLERS'] -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to PyPoE's documentation! 2 | ================================= 3 | 4 | PyPoE is collection of Python 3 based tools to work with Path of Exile. 5 | The target audience are both end users and developers. 6 | 7 | Goals of the Project 8 | ------------------------------------------------------------------------------ 9 | 10 | * provide a mostly complete and stable python3 api to work with Path of Exile 11 | and it's files 12 | * have a stable and useful UI binding to browse the files 13 | * provide CLI tools for extracting files for convince 14 | * provide utilities to publish the data on the Path of Exile wiki 15 | 16 | Core Features 17 | ------------------------------------------------------------------------------ 18 | 19 | * Development Library 20 | * Graphical User Interface (UI or GUI) 21 | * Command Line Interface (CLI) 22 | 23 | 24 | Getting Started 25 | ============================================================================== 26 | 27 | .. toctree:: 28 | 29 | installation 30 | report_issue 31 | contribution 32 | 33 | GUI 34 | ============================================================================== 35 | 36 | .. toctree:: 37 | 38 | GUI/overview 39 | GUI/ggpk_viewer 40 | 41 | CLI 42 | ============================================================================== 43 | 44 | .. toctree:: 45 | 46 | CLI/overview 47 | CLI/config 48 | CLI/setup 49 | CLI/dat 50 | CLI/wiki 51 | 52 | Developer Overview 53 | ============================================================================== 54 | 55 | .. toctree:: 56 | 57 | API/overview 58 | API/structure 59 | 60 | Indices and tables 61 | ================== 62 | 63 | * :ref:`genindex` 64 | * :ref:`modindex` 65 | * :ref:`search` 66 | 67 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/specifications/runtime_rowsize_mismatch.py: -------------------------------------------------------------------------------- 1 | """Overview 2 | =============================================================================== 3 | 4 | +----------+------------------------------------------------------------------+ 5 | | Path | tests/PyPoE/poe/file/_data/specifications/ | 6 | +----------+------------------------------------------------------------------+ 7 | | Version | 1.0.0a0 | 8 | +----------+------------------------------------------------------------------+ 9 | | Revision | $Id: f3932e2809b533c34ea76d3336ad483b5b4d190e $ | 10 | +----------+------------------------------------------------------------------+ 11 | | Author | Omega_K2 | 12 | +----------+------------------------------------------------------------------+ 13 | 14 | Description 15 | =============================================================================== 16 | 17 | Specification used for tests 18 | 19 | Agreement 20 | =============================================================================== 21 | 22 | See PyPoE/LICENSE 23 | 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | # 3rd-party 32 | 33 | # self 34 | from PyPoE.poe.file.specification.fields import * 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | specification = Specification({ 41 | 'Main.dat': File( 42 | fields=( 43 | Field( 44 | name='One', 45 | type='int', 46 | ), 47 | ), 48 | ), 49 | }) 50 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/test_path.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for PyPoE.poe.path 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | tests/PyPoE/poe/test_path.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 662ea8cf0006aa5ec28585bdd44d9d0f75c51017 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Tests for path.py 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | 27 | TODO 28 | =============================================================================== 29 | 30 | - The entire test I suppose 31 | """ 32 | 33 | # ============================================================================= 34 | # Imports 35 | # ============================================================================= 36 | 37 | # self 38 | from PyPoE.poe import path 39 | 40 | # ============================================================================= 41 | # Setup 42 | # ============================================================================= 43 | 44 | # ============================================================================= 45 | # Tests 46 | # ============================================================================= -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/test_ot.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for PyPoE.poe.file.ot 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | tests/PyPoE/poe/file/test_ot.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: ae9552f9db3b1c545c1402f12899905893992d09 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Tests for ot.py 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | 27 | TODO 28 | =============================================================================== 29 | 30 | - The entire test I suppose 31 | """ 32 | 33 | # ============================================================================= 34 | # Imports 35 | # ============================================================================= 36 | 37 | # self 38 | from PyPoE.poe.file import ot 39 | 40 | # ============================================================================= 41 | # Setup 42 | # ============================================================================= 43 | 44 | # ============================================================================= 45 | # Tests 46 | # ============================================================================= -------------------------------------------------------------------------------- /PyPoE/poe/sim/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | =============================================================================== 4 | 5 | +----------+------------------------------------------------------------------+ 6 | | Path | PyPoE/sim/__init__.py | 7 | +----------+------------------------------------------------------------------+ 8 | | Version | 1.0.0a0 | 9 | +----------+------------------------------------------------------------------+ 10 | | Revision | $Id: 738bbf451684ede5ce4e82d7e2467c805e81444e $ | 11 | +----------+------------------------------------------------------------------+ 12 | | Author | Omega_K2 | 13 | +----------+------------------------------------------------------------------+ 14 | 15 | Description 16 | =============================================================================== 17 | 18 | 19 | 20 | Agreement 21 | =============================================================================== 22 | 23 | See PyPoE/LICENSE 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | 32 | # 3rd-party 33 | 34 | # self 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | # ============================================================================= 41 | # Classes 42 | # ============================================================================= 43 | 44 | # ============================================================================= 45 | # Functions 46 | # ============================================================================= 47 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/specifications/invalid_enum_name.py: -------------------------------------------------------------------------------- 1 | """Overview 2 | =============================================================================== 3 | 4 | +----------+------------------------------------------------------------------+ 5 | | Path | tests/PyPoE/poe/file/_data/specifications/ | 6 | +----------+------------------------------------------------------------------+ 7 | | Version | 1.0.0a0 | 8 | +----------+------------------------------------------------------------------+ 9 | | Revision | $Id: aae1d367f6650954c2d368e0f0717b491ddf1501 $ | 10 | +----------+------------------------------------------------------------------+ 11 | | Author | Omega_K2 | 12 | +----------+------------------------------------------------------------------+ 13 | 14 | Description 15 | =============================================================================== 16 | 17 | Specification used for tests 18 | 19 | Agreement 20 | =============================================================================== 21 | 22 | See PyPoE/LICENSE 23 | 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | # 3rd-party 32 | 33 | # self 34 | from PyPoE.poe.file.specification.fields import * 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | specification = Specification({ 41 | 'Main.dat': File( 42 | fields=( 43 | Field( 44 | name='Invalid', 45 | type='int', 46 | enum='THIS_DOES_NOT_EXIST', 47 | ), 48 | ), 49 | ), 50 | }) 51 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/specifications/invalid_foreign_key_file.py: -------------------------------------------------------------------------------- 1 | """Overview 2 | =============================================================================== 3 | 4 | +----------+------------------------------------------------------------------+ 5 | | Path | tests/PyPoE/poe/file/_data/specifications/ | 6 | +----------+------------------------------------------------------------------+ 7 | | Version | 1.0.0a0 | 8 | +----------+------------------------------------------------------------------+ 9 | | Revision | $Id: cde9cc3dfd660cc8315eac346e2974f4b9b2607b $ | 10 | +----------+------------------------------------------------------------------+ 11 | | Author | Omega_K2 | 12 | +----------+------------------------------------------------------------------+ 13 | 14 | Description 15 | =============================================================================== 16 | 17 | Specification used for tests 18 | 19 | Agreement 20 | =============================================================================== 21 | 22 | See PyPoE/LICENSE 23 | 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | # 3rd-party 32 | 33 | # self 34 | from PyPoE.poe.file.specification.fields import * 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | specification = Specification({ 41 | 'Main.dat': File( 42 | fields=( 43 | Field( 44 | name='Missing', 45 | type='int', 46 | key='Missing.dat', 47 | ), 48 | ), 49 | ), 50 | }) 51 | -------------------------------------------------------------------------------- /PyPoE/poe/file/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | =============================================================================== 4 | 5 | +----------+------------------------------------------------------------------+ 6 | | Path | PyPoE/poe/file/__init__.py | 7 | +----------+------------------------------------------------------------------+ 8 | | Version | 1.0.0a0 | 9 | +----------+------------------------------------------------------------------+ 10 | | Revision | $Id: 561a9bb6527bd1744612684c1d6f756b654c6bc6 $ | 11 | +----------+------------------------------------------------------------------+ 12 | | Author | Omega_K2 | 13 | +----------+------------------------------------------------------------------+ 14 | 15 | Description 16 | =============================================================================== 17 | 18 | 19 | 20 | Agreement 21 | =============================================================================== 22 | 23 | See PyPoE/LICENSE 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | 32 | # 3rd-party 33 | 34 | # self 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | #__all__ = [] 41 | 42 | # ============================================================================= 43 | # Classes 44 | # ============================================================================= 45 | 46 | # ============================================================================= 47 | # Functions 48 | # ============================================================================= 49 | -------------------------------------------------------------------------------- /PyPoE/cli/exporter/wiki/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/cli/exporter/wiki/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 857f033447afcfa62ffc7c1b0a6e354b6d5e3cc1 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | 34 | # 3rd-party 35 | 36 | # self 37 | 38 | # ============================================================================= 39 | # Globals 40 | # ============================================================================= 41 | 42 | __all__ = [] 43 | 44 | # ============================================================================= 45 | # Classes 46 | # ============================================================================= 47 | 48 | # ============================================================================= 49 | # Functions 50 | # ============================================================================= 51 | -------------------------------------------------------------------------------- /PyPoE/cli/exporter/dat/parsers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/cli/exporter/dat/parsers/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 3e83b8dfea8344df6b504b7ea162a74088c6e95e $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | 34 | # 3rd-party 35 | 36 | # self 37 | 38 | # ============================================================================= 39 | # Globals 40 | # ============================================================================= 41 | 42 | __all__ = [] 43 | 44 | # ============================================================================= 45 | # Classes 46 | # ============================================================================= 47 | 48 | # ============================================================================= 49 | # Functions 50 | # ============================================================================= 51 | -------------------------------------------------------------------------------- /PyPoE/poe/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | =============================================================================== 4 | 5 | +----------+------------------------------------------------------------------+ 6 | | Path | poe/__init__.py | 7 | +----------+------------------------------------------------------------------+ 8 | | Version | 1.0.0a0 | 9 | +----------+------------------------------------------------------------------+ 10 | | Revision | $Id: 8e8aa1e56e696c0836081f8df9079ac77b0d7b08 $ | 11 | +----------+------------------------------------------------------------------+ 12 | | Author | Omega_K2 | 13 | +----------+------------------------------------------------------------------+ 14 | 15 | Description 16 | =============================================================================== 17 | 18 | All modules directly related to Path of Exile can be found in this package. 19 | 20 | Agreement 21 | =============================================================================== 22 | 23 | See PyPoE/LICENSE 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | 32 | # 3rd-party 33 | 34 | # self 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | # ============================================================================= 41 | # Classes 42 | # ============================================================================= 43 | 44 | # ============================================================================= 45 | # Functions 46 | # ============================================================================= 47 | -------------------------------------------------------------------------------- /PyPoE/shared/config/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities for config handling 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/shared/config/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 4a187a5caccb97ea31c6ddc607a3c7583d694d9f $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | 34 | # 3rd-party 35 | 36 | # self 37 | 38 | # ============================================================================= 39 | # Globals 40 | # ============================================================================= 41 | 42 | __all__ = [] 43 | 44 | # ============================================================================= 45 | # Classes 46 | # ============================================================================= 47 | 48 | # ============================================================================= 49 | # Functions 50 | # ============================================================================= 51 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/specifications/invalid_foreign_key_id.py: -------------------------------------------------------------------------------- 1 | """Overview 2 | =============================================================================== 3 | 4 | +----------+------------------------------------------------------------------+ 5 | | Path | tests/PyPoE/poe/file/_data/specifications/ | 6 | +----------+------------------------------------------------------------------+ 7 | | Version | 1.0.0a0 | 8 | +----------+------------------------------------------------------------------+ 9 | | Revision | $Id: 222b66633c41ebd83102d5814335325806e4f5c2 $ | 10 | +----------+------------------------------------------------------------------+ 11 | | Author | Omega_K2 | 12 | +----------+------------------------------------------------------------------+ 13 | 14 | Description 15 | =============================================================================== 16 | 17 | Specification used for tests 18 | 19 | Agreement 20 | =============================================================================== 21 | 22 | See PyPoE/LICENSE 23 | 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | # 3rd-party 32 | 33 | # self 34 | from PyPoE.poe.file.specification.fields import * 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | specification = Specification({ 41 | 'Main.dat': File( 42 | fields=( 43 | Field( 44 | name='ForeignKey', 45 | type='int', 46 | key='Other.dat', 47 | key_id='Missing', 48 | ), 49 | ), 50 | ), 51 | 'Other.dat': File( 52 | fields=( 53 | ), 54 | ), 55 | }) 56 | -------------------------------------------------------------------------------- /PyPoE/ui/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | UI Code 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/ui/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: a9cd438d188e21a7dfc68691d19c29be199f68db $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | 34 | # 3rd-party 35 | 36 | # self 37 | from PyPoE.ui.ggpk_viewer import GGPKViewerMainWindow 38 | from PyPoE.ui.launchpad import launchpad_main 39 | 40 | # ============================================================================= 41 | # Globals 42 | # ============================================================================= 43 | 44 | __all__ = [] 45 | 46 | _apps = [GGPKViewerMainWindow] 47 | 48 | # ============================================================================= 49 | # Entry point 50 | # ============================================================================= 51 | 52 | 53 | def main(*args, **kwargs): 54 | launchpad_main(_apps, *args, **kwargs) 55 | 56 | if __name__ == '__main__': 57 | main() -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/specifications/invalid_argument_combination.py: -------------------------------------------------------------------------------- 1 | """Overview 2 | =============================================================================== 3 | 4 | +----------+------------------------------------------------------------------+ 5 | | Path | tests/PyPoE/poe/file/_data/specifications/ | 6 | +----------+------------------------------------------------------------------+ 7 | | Version | 1.0.0a0 | 8 | +----------+------------------------------------------------------------------+ 9 | | Revision | $Id: 34560d9e0bf508a8d4b9651483d8e2143aaf56fc $ | 10 | +----------+------------------------------------------------------------------+ 11 | | Author | Omega_K2 | 12 | +----------+------------------------------------------------------------------+ 13 | 14 | Description 15 | =============================================================================== 16 | 17 | Specification used for tests 18 | 19 | Agreement 20 | =============================================================================== 21 | 22 | See PyPoE/LICENSE 23 | 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | # 3rd-party 32 | 33 | # self 34 | from PyPoE.poe.file.specification.fields import * 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | specification = Specification({ 41 | 'Main.dat': File( 42 | fields=( 43 | Field( 44 | name='Invalid', 45 | type='int', 46 | key='Other.dat', 47 | enum='MOD_DOMAIN', 48 | ), 49 | ), 50 | ), 51 | 'Other.dat': File( 52 | fields=( 53 | ), 54 | ), 55 | }) 56 | -------------------------------------------------------------------------------- /PyPoE/ui/shared/file/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shared UI code for file handling 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/ui/shared/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 6da613543dc70a3ec41224482edc53cafcfc4285 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Shared UI code for handling different file types and displaying them as 21 | QWidgets. 22 | 23 | Agreement 24 | =============================================================================== 25 | 26 | See PyPoE/LICENSE 27 | """ 28 | 29 | # ============================================================================= 30 | # Imports 31 | # ============================================================================= 32 | 33 | # Python 34 | 35 | # 3rd-party 36 | 37 | # self 38 | 39 | # ============================================================================= 40 | # Globals 41 | # ============================================================================= 42 | 43 | __all__ = [] 44 | 45 | # ============================================================================= 46 | # Classes 47 | # ============================================================================= 48 | 49 | # ============================================================================= 50 | # Functions 51 | # ============================================================================= 52 | -------------------------------------------------------------------------------- /PyPoE/cli/exporter/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | GGPK User Interface Classes 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/cli/exporter/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 73f93684a51fc454693960fa49e84abd5310e403 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Creates a qt User Interface to browse GGPK files. 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | import os 34 | import warnings 35 | 36 | # 3rd-Party 37 | from colorama import init 38 | 39 | # self 40 | from PyPoE import APP_DIR 41 | from PyPoE.cli.config import ConfigHelper 42 | from PyPoE.cli.core import OutputHook 43 | 44 | # ============================================================================= 45 | # Globals 46 | # ============================================================================= 47 | 48 | __all__ = ['CONFIG_PATH', 'config'] 49 | 50 | CONFIG_PATH = os.path.join(APP_DIR, 'exporter.conf') 51 | 52 | config = ConfigHelper(infile=CONFIG_PATH) 53 | 54 | # ============================================================================= 55 | # Init 56 | # ============================================================================= 57 | 58 | init() 59 | OutputHook(warnings.showwarning) 60 | -------------------------------------------------------------------------------- /PyPoE/ui/ggpk_viewer/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | GGPK Viewer code 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/ui/shared/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 31c2232508e109b9f99de037883d966c8bc9bbe1 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Specific code for the GGPK Viewer UI application. 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | 34 | # 3rd-party 35 | 36 | # self 37 | from PyPoE.ui.ggpk_viewer.core import GGPKViewerMainWindow 38 | from PyPoE.ui.shared import main 39 | 40 | # ============================================================================= 41 | # Globals 42 | # ============================================================================= 43 | 44 | __all__ = ['GGPKViewerMainWindow'] 45 | 46 | # ============================================================================= 47 | # Classes 48 | # ============================================================================= 49 | 50 | # ============================================================================= 51 | # Functions 52 | # ============================================================================= 53 | 54 | if __name__ == '__main__': 55 | main(GGPKViewerMainWindow) 56 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/test_psg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for PyPoE.poe.file.psg 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | tests/PyPoE/poe/file/test_psg.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: d2d9787cd2984d2ea8e55f23abdc6a952d815db3 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | from PyPoE.poe.file import psg 34 | from PyPoE.poe.file.dat import DatFile 35 | 36 | # 3rd-party 37 | import pytest 38 | 39 | # self 40 | 41 | # ============================================================================= 42 | # Setup 43 | # ============================================================================= 44 | 45 | # ============================================================================= 46 | # Fixtures 47 | # ============================================================================= 48 | 49 | # ============================================================================= 50 | # Tests 51 | # ============================================================================= 52 | 53 | 54 | def test_psg(file_system, rr): 55 | f = psg.PSGFile(passive_skills_dat_file=rr) 56 | f.read(file_system.get_file('Metadata/PassiveSkillGraph.psg')) -------------------------------------------------------------------------------- /docs/source/API/structure.rst: -------------------------------------------------------------------------------- 1 | Project Structure 2 | ============================================================================== 3 | 4 | The project is organized in various folders 5 | 6 | +-------------+---------------------------------------------------------------+ 7 | |Directory |Description | 8 | +-------------+---------------------------------------------------------------+ 9 | |/ |The root project folder. Only the very core files such as the | 10 | | |setup file or the LICENSE file should reside here. | 11 | +-------------+---------------------------------------------------------------+ 12 | |/docs/ |The root folder for the documentation. | 13 | | |It contains scripts and build files as well as the source | 14 | | |subdirectory | 15 | +-------------+---------------------------------------------------------------+ 16 | |/docs/source |Source documentation files and description that are not | 17 | | |contained in the python source files themselves. | 18 | | |Also contains templates and some generated files. | 19 | +-------------+---------------------------------------------------------------+ 20 | |/scripts |Collection of scripts intended to be invoked from the command | 21 | | |line. | 22 | | |Unlike the other items, the scripts do not ahere to the same | 23 | | |quality standards | 24 | +-------------+---------------------------------------------------------------+ 25 | |/tests/ |Tests for py.test | 26 | +-------------+---------------------------------------------------------------+ 27 | |/PyPoE/ |Root python folder | 28 | +-------------+---------------------------------------------------------------+ 29 | |/PyPoE/_data/|Shared data used by the other files. | 30 | +-------------+---------------------------------------------------------------+ -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/test_stat_filters.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for PyPoE.poe.file.stat_filters 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | tests/PyPoE/poe/file/test_stat_filters.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 73a63872a0cef473a4a183054c02a80dec64abaa $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | from PyPoE.poe.file import stat_filters 34 | 35 | # 3rd-party 36 | import pytest 37 | 38 | # self 39 | 40 | # ============================================================================= 41 | # Setup 42 | # ============================================================================= 43 | 44 | # ============================================================================= 45 | # Fixtures 46 | # ============================================================================= 47 | 48 | # ============================================================================= 49 | # Tests 50 | # ============================================================================= 51 | 52 | 53 | def test_stat_filter_file(file_system): 54 | f = stat_filters.StatFilterFile() 55 | f.read(file_system.get_file( 56 | 'Metadata/StatDescriptions/skillpopup_stat_filters.txt')) 57 | -------------------------------------------------------------------------------- /tests/PyPoE/shared/test_murmur2.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | tests/PyPoE/shared/test_murmur2.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: def8d0537d4a103fb96436b578feb3eb5e64f2be $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | 34 | # 3rd-party 35 | import pytest 36 | 37 | # self 38 | from PyPoE.shared import murmur2 39 | 40 | # ============================================================================= 41 | # Setup 42 | # ============================================================================= 43 | 44 | data = [ 45 | ('This is a test'.encode('ascii'), 895688205, 0), 46 | ('This is a test'.encode('ascii'), 1204582478, 42), 47 | ] 48 | 49 | # ============================================================================= 50 | # Fixtures 51 | # ============================================================================= 52 | 53 | # ============================================================================= 54 | # Tests 55 | # ============================================================================= 56 | 57 | @pytest.mark.parametrize('data,result,seed', data) 58 | def test_murmur2_32(data, result, seed): 59 | assert murmur2.murmur2_32(data, seed) == result -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/specifications/virtual_key_empty.py: -------------------------------------------------------------------------------- 1 | """Overview 2 | =============================================================================== 3 | 4 | +----------+------------------------------------------------------------------+ 5 | | Path | tests/PyPoE/poe/file/_data/specifications/ | 6 | +----------+------------------------------------------------------------------+ 7 | | Version | 1.0.0a0 | 8 | +----------+------------------------------------------------------------------+ 9 | | Revision | $Id: c2ce88db1ebe4d12e9ae4edbaa94aa93727a5dfc $ | 10 | +----------+------------------------------------------------------------------+ 11 | | Author | Omega_K2 | 12 | +----------+------------------------------------------------------------------+ 13 | 14 | Description 15 | =============================================================================== 16 | 17 | Specification used for tests 18 | 19 | Agreement 20 | =============================================================================== 21 | 22 | See PyPoE/LICENSE 23 | 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | # 3rd-party 32 | 33 | # self 34 | from PyPoE.poe.file.specification.fields import * 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | specification = Specification({ 41 | 'Main.dat': File( 42 | fields=( 43 | Field( 44 | name='One', 45 | type='int', 46 | ), 47 | Field( 48 | name='Two', 49 | type='int', 50 | ), 51 | Field( 52 | name='Three', 53 | type='int', 54 | ), 55 | Field( 56 | name='Four', 57 | type='int', 58 | ), 59 | ), 60 | virtual_fields=( 61 | VirtualField( 62 | name='Empty', 63 | fields=(''), 64 | ), 65 | ), 66 | ), 67 | }) 68 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/specifications/virtual_key_duplicate.py: -------------------------------------------------------------------------------- 1 | """Overview 2 | =============================================================================== 3 | 4 | +----------+------------------------------------------------------------------+ 5 | | Path | tests/PyPoE/poe/file/_data/specifications/ | 6 | +----------+------------------------------------------------------------------+ 7 | | Version | 1.0.0a0 | 8 | +----------+------------------------------------------------------------------+ 9 | | Revision | $Id: f26f3181fe4eee3b2c3ddb5d248ec500453799dc $ | 10 | +----------+------------------------------------------------------------------+ 11 | | Author | Omega_K2 | 12 | +----------+------------------------------------------------------------------+ 13 | 14 | Description 15 | =============================================================================== 16 | 17 | Specification used for tests 18 | 19 | Agreement 20 | =============================================================================== 21 | 22 | See PyPoE/LICENSE 23 | 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | # 3rd-party 32 | 33 | # self 34 | from PyPoE.poe.file.specification.fields import * 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | specification = Specification({ 41 | 'Main.dat': File( 42 | fields=( 43 | Field( 44 | name='One', 45 | type='int', 46 | ), 47 | Field( 48 | name='Two', 49 | type='int', 50 | ), 51 | Field( 52 | name='Three', 53 | type='int', 54 | ), 55 | Field( 56 | name='Four', 57 | type='int', 58 | ), 59 | ), 60 | virtual_fields=( 61 | VirtualField( 62 | name='One', 63 | fields=('Two', 'Three'), 64 | ), 65 | ), 66 | ), 67 | }) 68 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/specifications/virtual_key_invalid_key.py: -------------------------------------------------------------------------------- 1 | """Overview 2 | =============================================================================== 3 | 4 | +----------+------------------------------------------------------------------+ 5 | | Path | tests/PyPoE/poe/file/_data/specifications/ | 6 | +----------+------------------------------------------------------------------+ 7 | | Version | 1.0.0a0 | 8 | +----------+------------------------------------------------------------------+ 9 | | Revision | $Id: 720764bbb6d4770932449606b4a2c774ee1e783b $ | 10 | +----------+------------------------------------------------------------------+ 11 | | Author | Omega_K2 | 12 | +----------+------------------------------------------------------------------+ 13 | 14 | Description 15 | =============================================================================== 16 | 17 | Specification used for tests 18 | 19 | Agreement 20 | =============================================================================== 21 | 22 | See PyPoE/LICENSE 23 | 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | # 3rd-party 32 | 33 | # self 34 | from PyPoE.poe.file.specification.fields import * 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | specification = Specification({ 41 | 'Main.dat': File( 42 | fields=( 43 | Field( 44 | name='One', 45 | type='int', 46 | ), 47 | Field( 48 | name='Two', 49 | type='int', 50 | ), 51 | Field( 52 | name='Three', 53 | type='int', 54 | ), 55 | Field( 56 | name='Four', 57 | type='int', 58 | ), 59 | ), 60 | virtual_fields=( 61 | VirtualField( 62 | name='Virtual', 63 | fields=('Five', 'Six'), 64 | ), 65 | ), 66 | ), 67 | }) 68 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/specifications/virtual_key_invalid_data_type.py: -------------------------------------------------------------------------------- 1 | """Overview 2 | =============================================================================== 3 | 4 | +----------+------------------------------------------------------------------+ 5 | | Path | tests/PyPoE/poe/file/_data/specifications/ | 6 | +----------+------------------------------------------------------------------+ 7 | | Version | 1.0.0a0 | 8 | +----------+------------------------------------------------------------------+ 9 | | Revision | $Id: c71fa06143945285aa28a4f00ec883014c84823e $ | 10 | +----------+------------------------------------------------------------------+ 11 | | Author | Omega_K2 | 12 | +----------+------------------------------------------------------------------+ 13 | 14 | Description 15 | =============================================================================== 16 | 17 | Specification used for tests 18 | 19 | Agreement 20 | =============================================================================== 21 | 22 | See PyPoE/LICENSE 23 | 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | # 3rd-party 32 | 33 | # self 34 | from PyPoE.poe.file.specification.fields import * 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | specification = Specification({ 41 | 'Main.dat': File( 42 | fields=( 43 | Field( 44 | name='One', 45 | type='ref|list|int', 46 | ), 47 | Field( 48 | name='Two', 49 | type='int', 50 | ), 51 | Field( 52 | name='Three', 53 | type='int', 54 | ), 55 | Field( 56 | name='Four', 57 | type='int', 58 | ), 59 | ), 60 | virtual_fields=( 61 | VirtualField( 62 | name='Virtual', 63 | fields=('One', 'Two'), 64 | zip=True, 65 | ), 66 | ), 67 | ), 68 | }) 69 | -------------------------------------------------------------------------------- /PyPoE/cli/exporter/dat/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | .dat Exporter 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/cli/exporter/dat/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 303c7ee2d45962caea2d4e3307d0d68f350a2431 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | .dat Exporter 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | 34 | # 3rd-party 35 | 36 | # self 37 | from PyPoE.cli.exporter.dat.parsers.json import JSONExportHandler 38 | 39 | # ============================================================================= 40 | # Globals 41 | # ============================================================================= 42 | 43 | __all__ = ['DatHandler'] 44 | 45 | # ============================================================================= 46 | # Classes 47 | # ============================================================================= 48 | 49 | 50 | class DatHandler: 51 | """ 52 | 53 | :type sql: argparse.ArgumentParser 54 | """ 55 | def __init__(self, sub_parser): 56 | """ 57 | 58 | :type sub_parser: argparse._SubParsersAction 59 | """ 60 | parser = sub_parser.add_parser( 61 | 'dat', 62 | help='.dat export', 63 | ) 64 | parser.set_defaults(func=lambda args: parser.print_help()) 65 | 66 | sub = parser.add_subparsers(help='Export type') 67 | JSONExportHandler(sub) 68 | 69 | # ============================================================================= 70 | # Functions 71 | # ============================================================================= 72 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/test_ggpk.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for PyPoE.poe.file.ggpk 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | tests/PyPoE/poe/file/test_ggpk.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: cd1aac1f3518648a723bc9a41de1ac71a050e24b $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Tests for ggpk.py 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | 27 | .. todo:: 28 | 29 | * The entire test I suppose 30 | """ 31 | 32 | # ============================================================================= 33 | # Imports 34 | # ============================================================================= 35 | 36 | # Python 37 | import os 38 | from tempfile import TemporaryDirectory 39 | 40 | # 3rd Party 41 | import pytest 42 | 43 | # self 44 | 45 | # ============================================================================= 46 | # Setup 47 | # ============================================================================= 48 | 49 | DDS_UNCOMPRESSED = 'Art/Textures/Characters/Adventurer/' \ 50 | 'adventurerPalid_colour.dds' 51 | DDS_COMPRESSED = 'Art/2DArt/BuffIcons/AssassinsMark.dds' 52 | 53 | # ============================================================================= 54 | # Tests 55 | # ============================================================================= 56 | 57 | 58 | # These tests will raise errors if something is wrong, like decompression 59 | # errors 60 | class TestDDSExtract: 61 | def test_uncompressed(self, file_system): 62 | file_system.extract_dds(file_system.get_file(DDS_UNCOMPRESSED)) 63 | 64 | def test_compressed(self, file_system): 65 | file_system.extract_dds(file_system.get_file(DDS_COMPRESSED)) 66 | 67 | def test_reference(self, file_system): 68 | data = b'*' + DDS_COMPRESSED.encode('ascii') 69 | file_system.extract_dds(data) 70 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/specifications/runtime_missing_foreign_key1.py: -------------------------------------------------------------------------------- 1 | """Overview 2 | =============================================================================== 3 | 4 | +----------+------------------------------------------------------------------+ 5 | | Path | tests/PyPoE/poe/file/_data/specifications/ | 6 | +----------+------------------------------------------------------------------+ 7 | | Version | 1.0.0a0 | 8 | +----------+------------------------------------------------------------------+ 9 | | Revision | $Id: fe7d9dce2ddaa7bc9584e8f14079ea28f98865f1 $ | 10 | +----------+------------------------------------------------------------------+ 11 | | Author | Omega_K2 | 12 | +----------+------------------------------------------------------------------+ 13 | 14 | Description 15 | =============================================================================== 16 | 17 | Specification used for tests 18 | 19 | Agreement 20 | =============================================================================== 21 | 22 | See PyPoE/LICENSE 23 | 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | # 3rd-party 32 | 33 | # self 34 | from PyPoE.poe.file.specification.fields import * 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | specification = Specification({ 41 | 'Main.dat': File( 42 | fields=( 43 | Field( 44 | name='ForeignKey', 45 | type='int', 46 | ), 47 | Field( 48 | name='ForeignKeyOffset', 49 | type='int', 50 | ), 51 | Field( 52 | name='ForeignKeyMismatch', 53 | type='int', 54 | key='Other.dat', 55 | ), 56 | Field( 57 | name='ForeignKeyNone', 58 | type='int', 59 | ), 60 | Field( 61 | name='ForeignKeyCellValue', 62 | type='int', 63 | ), 64 | Field( 65 | name='ConstTest', 66 | type='int', 67 | ), 68 | ), 69 | ), 70 | 'Other.dat': File( 71 | fields=( 72 | Field( 73 | name='Value', 74 | type='int', 75 | unique=True, 76 | ), 77 | ), 78 | ), 79 | }) 80 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/specifications/runtime_missing_foreign_key2.py: -------------------------------------------------------------------------------- 1 | """Overview 2 | =============================================================================== 3 | 4 | +----------+------------------------------------------------------------------+ 5 | | Path | tests/PyPoE/poe/file/_data/specifications/ | 6 | +----------+------------------------------------------------------------------+ 7 | | Version | 1.0.0a0 | 8 | +----------+------------------------------------------------------------------+ 9 | | Revision | $Id: d6e95580decd5df4b51ec4ab0b4e6b759757bbbf $ | 10 | +----------+------------------------------------------------------------------+ 11 | | Author | Omega_K2 | 12 | +----------+------------------------------------------------------------------+ 13 | 14 | Description 15 | =============================================================================== 16 | 17 | Specification used for tests 18 | 19 | Agreement 20 | =============================================================================== 21 | 22 | See PyPoE/LICENSE 23 | 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | # 3rd-party 32 | 33 | # self 34 | from PyPoE.poe.file.specification.fields import * 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | specification = Specification({ 41 | 'Main.dat': File( 42 | fields=( 43 | Field( 44 | name='ForeignKey', 45 | type='int', 46 | key='Other.dat', 47 | key_id='Value', 48 | ), 49 | Field( 50 | name='ForeignKeyOffset', 51 | type='int', 52 | ), 53 | Field( 54 | name='ForeignKeyMismatch', 55 | type='int', 56 | ), 57 | Field( 58 | name='ForeignKeyNone', 59 | type='int', 60 | ), 61 | Field( 62 | name='ForeignKeyCellValue', 63 | type='int', 64 | ), 65 | Field( 66 | name='ConstTest', 67 | type='int', 68 | ), 69 | ), 70 | ), 71 | 'Other.dat': File( 72 | fields=( 73 | Field( 74 | name='Value', 75 | type='int', 76 | unique=True, 77 | ), 78 | ), 79 | ), 80 | }) 81 | -------------------------------------------------------------------------------- /PyPoE/shared/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shared Python code 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/shared/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 3c33a22c2168fb9abc0f36f4f402574bb6a11507 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | 34 | # 3rd-party 35 | 36 | # self 37 | 38 | # ============================================================================= 39 | # Globals 40 | # ============================================================================= 41 | 42 | __all__ = [] 43 | 44 | # ============================================================================= 45 | # Classes 46 | # ============================================================================= 47 | 48 | class InheritedDocStringsMeta(type): 49 | def __new__(cls, name, args, attrs): 50 | if not ('__doc__' in attrs and attrs['__doc__']): 51 | for mro in cls.mro(cls): 52 | docstring = mro.__doc__ 53 | if docstring is not None: 54 | cls.__doc__ = docstring 55 | #attrs['__doc__'] = docstring 56 | break 57 | for attr, attribute in attrs.items(): 58 | if attribute.__doc__: 59 | continue 60 | 61 | for mro in cls.mro(cls): 62 | if not hasattr(mro, attr): 63 | break 64 | docstring = getattr(mro, attr).__doc__ 65 | if docstring is not None: 66 | attribute.__doc__ = docstring 67 | break 68 | 69 | return type.__new__(cls, name, args, attrs) 70 | 71 | # ============================================================================= 72 | # Functions 73 | # ============================================================================= 74 | -------------------------------------------------------------------------------- /PyPoE/cli/exporter/util.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility functions for exporters 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/cli/exporter/util.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 3638c55f4e1d3e1b1d3b17e13013af730a47056e $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Utility functions for exporters. 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | import re 34 | 35 | # self 36 | from PyPoE.poe.path import PoEPath 37 | from PyPoE.cli.config import SetupError 38 | from PyPoE.cli.exporter import config 39 | 40 | # ============================================================================= 41 | # Globals 42 | # ============================================================================= 43 | 44 | __all__ = [ 45 | 'get_content_path', 46 | 'fix_path', 47 | ] 48 | 49 | # ============================================================================= 50 | # Functions 51 | # ============================================================================= 52 | 53 | 54 | def get_content_path(): 55 | """ 56 | Returns the path to the current content.ggpk based on the specified 57 | config variables for the version & distributor. 58 | 59 | :return: Path of the content ggpk 60 | :rtype: str 61 | 62 | :raises SetupError: if no valid path was found. 63 | """ 64 | path = config.get_option('ggpk_path') 65 | if path == '': 66 | args = config.get_option('version'), config.get_option('distributor') 67 | paths = PoEPath(*args).get_installation_paths() 68 | 69 | if not paths: 70 | raise SetupError('No PoE Installation found.') 71 | 72 | return paths[0] 73 | else: 74 | return path 75 | 76 | 77 | def fix_path(path: str) -> str: 78 | if re.match('[a-zA-Z]:.*', path): 79 | return path[:2] + re.sub(r':', '_', path[2:]) 80 | else: 81 | return path -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/specifications/rr_test.py: -------------------------------------------------------------------------------- 1 | """Overview 2 | =============================================================================== 3 | 4 | +----------+------------------------------------------------------------------+ 5 | | Path | tests/PyPoE/poe/file/_data/specifications/ | 6 | +----------+------------------------------------------------------------------+ 7 | | Version | 1.0.0a0 | 8 | +----------+------------------------------------------------------------------+ 9 | | Revision | $Id: a634dde2ce35c6473e99f7a1c72eb7e1d941a743 $ | 10 | +----------+------------------------------------------------------------------+ 11 | | Author | Omega_K2 | 12 | +----------+------------------------------------------------------------------+ 13 | 14 | Description 15 | =============================================================================== 16 | 17 | Specification used for tests 18 | 19 | Agreement 20 | =============================================================================== 21 | 22 | See PyPoE/LICENSE 23 | 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | 32 | # 3rd-party 33 | 34 | # self 35 | from PyPoE.poe.file.specification.fields import * 36 | 37 | # ============================================================================= 38 | # Globals 39 | # ============================================================================= 40 | 41 | specification = Specification({ 42 | 'Main.dat': File( 43 | fields=( 44 | Field( 45 | name='ForeignKey', 46 | type='int', 47 | key='Other.dat', 48 | ), 49 | Field( 50 | name='ForeignKeyOffset', 51 | type='int', 52 | key='Other.dat', 53 | key_offset=1, 54 | ), 55 | Field( 56 | name='ForeignKeyMismatch', 57 | type='int', 58 | ), 59 | Field( 60 | name='ForeignKeyNone', 61 | type='int', 62 | key='Other.dat', 63 | ), 64 | Field( 65 | name='ForeignKeyCellValue', 66 | type='int', 67 | key='Other.dat', 68 | key_id='Value', 69 | ), 70 | Field( 71 | name='ConstTest', 72 | type='int', 73 | enum='MOD_DOMAIN', 74 | ), 75 | ), 76 | ), 77 | 'Other.dat': File( 78 | fields=( 79 | Field( 80 | name='Value', 81 | type='int', 82 | unique=True, 83 | ), 84 | ), 85 | ), 86 | }) 87 | -------------------------------------------------------------------------------- /PyPoE/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Library init 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 6e0bd244fe7407e3228c5cafe6b4b56524f8d362 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Library Init 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | import platform 34 | import os 35 | import warnings 36 | 37 | # ============================================================================= 38 | # Globals 39 | # ============================================================================= 40 | 41 | __all__ = [ 42 | 'APP_DIR', 43 | 'DIR', 44 | 'DATA_DIR', 45 | ] 46 | __version__ = '1.0.0a0' 47 | 48 | # ============================================================================= 49 | # Functions 50 | # ============================================================================= 51 | 52 | def _get_app_dir(): 53 | osys = platform.system() 54 | if osys == 'Windows': 55 | vars = ['APPDATA'] 56 | subdir = 'PyPoE' 57 | elif osys == 'Linux': 58 | vars = ['HOME', 'PWD'] 59 | subdir = '.PyPoE' 60 | else: 61 | raise RuntimeError('Unsupported Operating System') 62 | 63 | dir = None 64 | for var in vars: 65 | if var not in os.environ: 66 | continue 67 | dir = os.environ[var] 68 | if not os.path.exists(dir): 69 | continue 70 | break 71 | 72 | if dir is None: 73 | raise RuntimeError('Home/user directory not found') 74 | 75 | dir = os.path.join(dir, subdir) 76 | if not os.path.exists(dir): 77 | os.mkdir(dir) 78 | 79 | return dir 80 | 81 | # ============================================================================= 82 | # Init 83 | # ============================================================================= 84 | 85 | warnings.simplefilter('default', DeprecationWarning) 86 | APP_DIR = _get_app_dir() 87 | DIR = os.path.join(os.path.dirname(__file__)) 88 | DATA_DIR = os.path.join(DIR, '_data') 89 | -------------------------------------------------------------------------------- /tests/PyPoE/shared/test_mixins.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | tests/PyPoE/shared/test_mixins.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 50179bff0b3569b7ea5d02ec993a82aecf2a01a1 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | from collections import OrderedDict 34 | 35 | # 3rd-party 36 | import pytest 37 | 38 | # self 39 | from PyPoE.shared import mixins 40 | 41 | # ============================================================================= 42 | # Setup 43 | # ============================================================================= 44 | 45 | 46 | class Repr(mixins.ReprMixin): 47 | def __init__(self, a, b=42): 48 | self.a = a 49 | self._b = b 50 | self.extra = 1337 51 | 52 | 53 | # ============================================================================= 54 | # Tests 55 | # ============================================================================= 56 | 57 | 58 | class TestReprMixin: 59 | 60 | @pytest.fixture 61 | def r(self): 62 | return Repr(5) 63 | 64 | def test_defaults(self, r): 65 | assert repr(r) == 'Repr<%s>(a=5)' % hex(id(r)) 66 | 67 | def test_private(self, r): 68 | r._REPR_PRIVATE_ATTRIBUTES = True 69 | assert repr(r) == 'Repr<%s>(a=5, b=42)' % hex(id(r)) 70 | 71 | def test_override(self, r): 72 | r._REPR_ARGUMENTS_TO_ATTRIBUTES = {'b': 'extra'} 73 | assert repr(r) == 'Repr<%s>(a=5, b=1337)' % hex(id(r)) 74 | 75 | def test_override_missing(self, r): 76 | r._REPR_ARGUMENTS_TO_ATTRIBUTES = {'b': 'extra'} 77 | r._REPR_ARGUMENTS_IGNORE_MISSING = True 78 | assert repr(r) == 'Repr<%s>(b=1337)' % hex(id(r)) 79 | 80 | def test_ingore(self, r): 81 | r._REPR_ARGUMENTS_IGNORE = {'a'} 82 | assert repr(r) == 'Repr<%s>()' % hex(id(r)) 83 | 84 | def test_extra_attributes(self, r): 85 | r._REPR_EXTRA_ATTRIBUTES = OrderedDict((('b', '_b',), ('extra', None))) 86 | assert repr(r) == 'Repr<%s>(a=5, b=42, extra=1337)' % hex(id(r)) -------------------------------------------------------------------------------- /docs/generate_rst_templates.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | =============================================================================== 4 | 5 | +----------+------------------------------------------------------------------+ 6 | | Path | genmodules.py | 7 | +----------+------------------------------------------------------------------+ 8 | | Version | 1.0.0a0 | 9 | +----------+------------------------------------------------------------------+ 10 | | Revision | $Id: 3e1d2914e3ed8f906768d089cb419a5a14fea4d4 $ | 11 | +----------+------------------------------------------------------------------+ 12 | | Author | Omega_K2 | 13 | +----------+------------------------------------------------------------------+ 14 | 15 | Description 16 | =============================================================================== 17 | 18 | 19 | 20 | Agreement 21 | =============================================================================== 22 | 23 | See PyPoE/LICENSE 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | import os 32 | import re 33 | 34 | # 3rd-party 35 | 36 | # self 37 | import PyPoE 38 | 39 | # ============================================================================= 40 | # Globals 41 | # ============================================================================= 42 | 43 | __all__ = [] 44 | 45 | # ============================================================================= 46 | # Classes 47 | # ============================================================================= 48 | 49 | # ============================================================================= 50 | # Functions 51 | # ============================================================================= 52 | 53 | if __name__ == '__main__': 54 | curdir = os.path.split(__file__)[0] 55 | 56 | outpaths = [] 57 | 58 | for dirpath, dirnames, filenames in os.walk(PyPoE.DIR): 59 | for item in list(dirnames): 60 | if item.startswith('.') or item.startswith('__pycache'): 61 | dirnames.remove(item) 62 | 63 | for item in list(filenames): 64 | if item.startswith('.') or not item.endswith('.py'): 65 | filenames.remove(item) 66 | 67 | pypoe_path = dirpath.replace(PyPoE.DIR, 'PyPoE').strip('\\/') 68 | for filename in filenames: 69 | path = re.split(r'\\|/', os.path.join(pypoe_path, filename)) 70 | if path[-1] == '__init__.py': 71 | del path[-1] 72 | 73 | path[-1] = path[-1].replace('.py', '') 74 | 75 | outpaths.append('.'.join(path)) 76 | 77 | outpaths.sort() 78 | with open(os.path.join(curdir, 'source', 'autosummary.rst'), 'w') as f: 79 | f.write('.. autosummary::\n') 80 | f.write(' :toctree: _autosummary\n \n') 81 | for path in outpaths: 82 | f.write(' %s\n' % path) 83 | -------------------------------------------------------------------------------- /scripts/profile/PyPoE/poe/file/translations.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | translations.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 77c2b3ac5feb62418156236485f5de6157495477 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | 34 | # 3rd-party 35 | from line_profiler import LineProfiler 36 | 37 | # self 38 | from PyPoE.poe.file import translations 39 | 40 | # ============================================================================= 41 | # Globals 42 | # ============================================================================= 43 | 44 | __all__ = [] 45 | 46 | # ============================================================================= 47 | # Classes 48 | # ============================================================================= 49 | 50 | # ============================================================================= 51 | # Functions 52 | # ============================================================================= 53 | 54 | # ============================================================================= 55 | # Init 56 | # ============================================================================= 57 | 58 | if __name__ == '__main__': 59 | profiler = LineProfiler() 60 | 61 | #profiler.add_function(translations.TranslationFile.get_translation) 62 | #profiler.add_function(translations.TranslationFile._read) 63 | profiler.add_function(translations.TranslationString._set_string) 64 | #profiler.add_function(translations.Translation.get_language) 65 | #profiler.add_function(translations.TranslationQuantifier.handle) 66 | #profiler.add_function(translations.TranslationRange.in_range) 67 | #profiler.add_function(translations.TranslationLanguage.get_string) 68 | 69 | profiler.run("s = translations.TranslationFile('C:/Temp/MetaData/stat_descriptions.txt')") 70 | profiler.run("for i in range(0, 100): t = s.get_translation(tags=['additional_chance_to_take_critical_strike_%', 'additional_chance_to_take_critical_strike_%'], values=((3, 5), 6))") 71 | 72 | profiler.print_stats() 73 | 74 | print('translations.Translation:', t) -------------------------------------------------------------------------------- /scripts/profile/PyPoE/ui/dat_handler.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | scripts/profile/PyPoE/ui/dat_handler.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: a087795ef96bfa1d715075182180d53d2712325c $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | import sys 34 | 35 | # 3rd-party 36 | import cProfile 37 | #from line_profiler import LineProfiler 38 | 39 | from PySide2.QtCore import * 40 | from PySide2.QtWidgets import * 41 | 42 | # self 43 | from PyPoE.poe.file.file_system import FileSystem 44 | from PyPoE.ui.shared.file.handler import DatStyle 45 | from PyPoE.ui.shared.file.manager import FileDataManager 46 | 47 | # ============================================================================= 48 | # Globals 49 | # ============================================================================= 50 | 51 | __all__ = [] 52 | 53 | # ============================================================================= 54 | # Classes 55 | # ============================================================================= 56 | 57 | # ============================================================================= 58 | # Functions 59 | # ============================================================================= 60 | if __name__ == '__main__': 61 | '''profiler = LineProfiler( 62 | DatStyle.sizeHint, 63 | DatStyle._get_text, 64 | DatStyle._show_value, 65 | )''' 66 | translator = QTranslator() 67 | translator.load('i18n/en_US') 68 | app = QApplication(sys.argv) 69 | app.installTranslator(translator) 70 | frame = QMainWindow() 71 | frame.setMinimumSize(2000, 1000) 72 | 73 | fs = FileSystem(r'M:\Steam\steamapps\common\Path of Exile') 74 | 75 | f = 'HeistEquipment.dat' 76 | 77 | data = fs.get_file('Data/' + f) 78 | #for item in dir(o): 79 | # print(item, getattr(o, item)) 80 | fm = FileDataManager(None) 81 | h = fm.get_handler(f) 82 | #profiler.run('w = h.get_widget(data, f, parent=frame)') 83 | #profiler.print_stats() 84 | w = h.get_widget(data, f, parent=frame) 85 | frame.setCentralWidget(w) 86 | 87 | frame.show() 88 | app.exec_() 89 | -------------------------------------------------------------------------------- /PyPoE/cli/exporter/wiki/parsers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Parser package init 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/cli/exporter/wiki/parsers/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 5cd2965157c6584e90bc4d5aba1baed1a370eeaf $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Automatically finds all ExporterHandlers in the package files and import them 21 | into WIKI_HANDLERS. 22 | 23 | Agreement 24 | =============================================================================== 25 | 26 | See PyPoE/LICENSE 27 | """ 28 | 29 | 30 | # ============================================================================= 31 | # Imports 32 | # ============================================================================= 33 | 34 | 35 | # Python 36 | import os 37 | from importlib import import_module 38 | 39 | # self 40 | from PyPoE.cli.exporter.wiki.handler import ExporterHandler 41 | 42 | 43 | # ============================================================================= 44 | # Globals 45 | # ============================================================================= 46 | 47 | 48 | WIKI_HANDLERS = [] 49 | 50 | __all__ = ['WIKI_HANDLERS'] 51 | 52 | 53 | # ============================================================================= 54 | # Funcs 55 | # ============================================================================= 56 | 57 | 58 | def _load(): 59 | cur_dir = os.path.split(os.path.realpath(__file__))[0] 60 | for file_name in os.listdir(cur_dir): 61 | if file_name.startswith('_'): 62 | continue 63 | file_name = file_name.replace('.py', '') 64 | imp = import_module('.' + file_name, __package__) 65 | for obj_name in dir(imp): 66 | if obj_name.startswith('_'): 67 | continue 68 | 69 | if not obj_name.endswith('Handler'): 70 | continue 71 | 72 | obj = getattr(imp, obj_name) 73 | 74 | # Not a class 75 | if not isinstance(obj, type): 76 | continue 77 | 78 | # Only export handlers 79 | if not issubclass(obj, ExporterHandler): 80 | continue 81 | 82 | # Only subclasses of which 83 | if obj is ExporterHandler: 84 | continue 85 | 86 | WIKI_HANDLERS.append(obj) 87 | 88 | 89 | # ============================================================================= 90 | # Init 91 | # ============================================================================= 92 | 93 | 94 | _load() -------------------------------------------------------------------------------- /scripts/profile/PyPoE/poe/file/ot.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | scripts/profile/PyPoE/poe/file/ot.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: e6321c87ce6c39086d605683c804d3b7bab8d7a5 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | import os 34 | 35 | # 3rd-party 36 | import line_profiler 37 | 38 | # self 39 | from PyPoE.poe.file.shared.keyvalues import * 40 | from PyPoE.poe.file.ot import OTFile 41 | 42 | # ============================================================================= 43 | # Globals 44 | # ============================================================================= 45 | 46 | __all__ = [] 47 | 48 | # ============================================================================= 49 | # Classes 50 | # ============================================================================= 51 | 52 | # ============================================================================= 53 | # Functions 54 | # ============================================================================= 55 | 56 | # ============================================================================= 57 | # Init 58 | # ============================================================================= 59 | 60 | if __name__ == '__main__': 61 | profiler = line_profiler.LineProfiler() 62 | profiler.add_function(AbstractKeyValueFile._read) 63 | profiler.add_function(AbstractKeyValueFile.__missing__) 64 | 65 | def run(): 66 | f = 'C:/Temp/' 67 | sections = set() 68 | for path, dirnames, filenames in os.walk(f): 69 | for filename in filenames: 70 | if not filename.endswith('.ot'): 71 | continue 72 | 73 | ot = OTFile(parent_or_base_dir_or_ggpk=f) 74 | ot.read(os.path.join(path, filename)) 75 | for k in ot.keys(): 76 | sections.add(k) 77 | 78 | sections = list(sections) 79 | sections.sort() 80 | print(sections) 81 | 82 | 83 | profiler.run('run()') 84 | 85 | #ot = OTFile(parent_or_base_dir_or_ggpk=f) 86 | #ot.read('C:\Temp\Metadata\Items\Armours\BodyArmours\AbstractBodyArmour.ot') 87 | #print(ot.keys()) 88 | 89 | profiler.print_stats() -------------------------------------------------------------------------------- /docs/source/contribution.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | =============================================================================== 3 | 4 | Everyone is generally welcome to contribute to the project. 5 | 6 | There are basically 3 ways to do so: 7 | 8 | * Feature requests & :doc:`reporting issues ` 9 | * Sending pull requests 10 | * Becoming a dev 11 | 12 | Pull requests 13 | ------------------------------------------------------------------------------- 14 | 15 | You're welcome to send a pull request on github for features or changes you 16 | think that should be in PyPoE. 17 | 18 | Please keep a few things in mind: 19 | 20 | * the code in the request should be well behaved and a proper fix or addition 21 | * the change should be under the MIT license 22 | * if it's an entirely new feature, it may debatable whether it is has a place 23 | in PyPoE 24 | * the submitted code should: 25 | * be well behaved and a proper fix or addition 26 | * be PEP8 compatible (minus the line-length) 27 | * include changes in the respective tests or new tests 28 | * validate against existing tests (if tests were changed, validate against 29 | those) 30 | * backwards incompatible changes are more suited for the dev branch 31 | 32 | So for example: 33 | 34 | * Likely to be accepted 35 | 36 | * general improvements to existing code of UI, CLI or API 37 | * new, well-behaved features that extend the existing functional 38 | * support for missing file formats 39 | * updated dat.specification.ini 40 | * additional tests 41 | 42 | * Likely to be rejected 43 | 44 | * changes unrelated to the goal of the project 45 | * refusal of making the change itself available under MIT license 46 | * changes with poor coding style 47 | 48 | Become a dev 49 | ------------------------------------------------------------------------------- 50 | 51 | If you want to become an active developer and meet the requirements please 52 | contact me. I'll manually unlock people for access to the repo. 53 | 54 | .. note:: 55 | 56 | If you just want to contribute a few changes, there is no need to become a 57 | dev and you can send pull requests instead. 58 | 59 | If you just want your own repo, just fork the project on github. 60 | 61 | **Requirements** 62 | 63 | * You should 64 | 65 | * have a good amount of experience with developing in Python 3 66 | * be willing to actively contribute, i.e. making changes on your own, working 67 | on the TODOs 68 | * be fluent in English (written); no you don't have to be a perfect speaker, 69 | but you're English should be good enough to have no issues with 70 | communication 71 | * be available in the IRC channel 72 | * I'll want to see some pieces of code you've written; a good history of 73 | pull requests will suffice, otherwise: 74 | 75 | * another open source project you've been involved in, 76 | * some private things you've written but are willing to let me have a look at 77 | 78 | It would be extra helpful, if you: 79 | 80 | * have experience with C/C++ and writing embedded python libraries with C/API 81 | * have a lot of experience with Path of Exile [for the api] 82 | * are experienced with using Mediawiki [for exporter] 83 | * speak other languages fluently or natively [for translating the project] 84 | * are adept with reverse engineering [to help with api] 85 | -------------------------------------------------------------------------------- /PyPoE/shared/murmur2.py: -------------------------------------------------------------------------------- 1 | """ 2 | MurmurHash2 Python Implementation 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/shared/murmur2.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 31aaffcb3e749b62620728ac09d424d6070af5e4 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | A pure python implementation of the MurmurHash2 algorithm by Austin Appleby. 21 | See also: https://code.google.com/p/smhasher/wiki/MurmurHash 22 | 23 | Agreement 24 | =============================================================================== 25 | 26 | See PyPoE/LICENSE 27 | """ 28 | 29 | # ============================================================================= 30 | # Imports 31 | # ============================================================================= 32 | 33 | import struct 34 | 35 | # ============================================================================= 36 | # Globals & Constants 37 | # ============================================================================= 38 | 39 | DEFAULT_SEED = 0 40 | # 'm' and 'r' are mixing constants generated offline. 41 | # They're not really 'magic', they just happen to work well. 42 | M = 0x5bd1e995 43 | R = 24 44 | 45 | int32 = 0xFFFFFFFF 46 | 47 | # ============================================================================= 48 | # Functions 49 | # ============================================================================= 50 | 51 | 52 | def murmur2_32(byte_data, seed=DEFAULT_SEED): 53 | """ 54 | Creates a murmur2 32 bit integer hash from the given byte_data and seed. 55 | 56 | :param bytes byte_data: the bytes to hash 57 | :param int seed: seed to initialize this with 58 | :return int: 32 bit hash 59 | """ 60 | 61 | length = len(byte_data) 62 | # Initialize the hash to a 'random' value 63 | h = (seed ^ length) & int32 64 | 65 | # Mix 4 bytes at a time into the hash 66 | index = 0 67 | 68 | while length >= 4: 69 | k = struct.unpack('> R & int32) 73 | k = k * M & int32 74 | 75 | h = h * M & int32 76 | h = (h ^ k) & int32 77 | 78 | index += 4 79 | length -= 4 80 | 81 | # Handle the last few bytes of the input array 82 | if length >= 3: 83 | h = (h ^ byte_data[index+2] << 16) & int32 84 | if length >= 2: 85 | h = (h ^ byte_data[index+1] << 8) & int32 86 | if length >= 1: 87 | h = (h ^ byte_data[index]) & int32 88 | h = h * M & int32 89 | 90 | # Do a few final mixes of the hash to ensure the last few bytes are 91 | # well-incorporated. 92 | h = h ^ (h >> 13 & int32) 93 | h = h * M & int32 94 | h = h ^ (h >> 15 & int32) 95 | 96 | return h -------------------------------------------------------------------------------- /PyPoE/cli/exporter/wiki/core.py: -------------------------------------------------------------------------------- 1 | """ 2 | Core Wiki Exporter 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/cli/exporter/wiki/core.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: c7fc425193296ca78844b5956d4f95564f9728e1 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Core Wiki Exporter 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | 34 | # self 35 | from PyPoE.cli.core import console, Msg 36 | from PyPoE.cli.handler import BaseHandler 37 | from PyPoE.cli.exporter import config 38 | from PyPoE.cli.exporter.wiki.parsers import WIKI_HANDLERS 39 | from PyPoE.cli.exporter.wiki.admin import ADMIN_HANDLERS 40 | 41 | # ============================================================================= 42 | # Globals 43 | # ============================================================================= 44 | 45 | __all__ = ['WikiHandler'] 46 | 47 | # ============================================================================= 48 | # Classes 49 | # ============================================================================= 50 | 51 | 52 | class WikiHandler(BaseHandler): 53 | def __init__(self, sub_parser): 54 | # Config Options 55 | config.add_option('temp_dir', 'is_directory(exists=True)') 56 | config.add_option('out_dir', 'is_directory(exists=True)') 57 | config.register_setup('temp_dir', self._setup) 58 | config.add_setup_variable('temp_dir', 'hash', 'string(default="")') 59 | config.add_setup_listener('version', self._ver_dist_changed) 60 | config.add_setup_listener('ggpk_path', self._ver_dist_changed) 61 | 62 | # Parser 63 | self.parser = sub_parser.add_parser('wiki', help='Wiki Exporter') 64 | self.parser.set_defaults(func=lambda args: self.parser.print_help()) 65 | wiki_sub = self.parser.add_subparsers() 66 | 67 | for handler in WIKI_HANDLERS: 68 | handler(wiki_sub) 69 | 70 | for handler in ADMIN_HANDLERS: 71 | handler(wiki_sub) 72 | 73 | def _ver_dist_changed(self, key, value, old_value): 74 | if value == old_value: 75 | return 76 | config.set_setup_variable('temp_dir', 'performed', False) 77 | console('Setup needs to be performed due to changes to "%s"' % key, 78 | msg=Msg.warning) 79 | 80 | def _setup(self, args): 81 | """ 82 | :param args: argparse args passed on 83 | :return: 84 | """ 85 | console('Done.') 86 | 87 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/test_text.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | =============================================================================== 4 | 5 | +----------+------------------------------------------------------------------+ 6 | | Path | tests/PyPoE/poe/test_text.py | 7 | +----------+------------------------------------------------------------------+ 8 | | Version | 1.0.0a0 | 9 | +----------+------------------------------------------------------------------+ 10 | | Revision | $Id: 6d9d0d91937791a009d978957b25760e7bfd2606 $ | 11 | +----------+------------------------------------------------------------------+ 12 | | Author | Omega_K2 | 13 | +----------+------------------------------------------------------------------+ 14 | 15 | Description 16 | =============================================================================== 17 | 18 | Tests for PyPoE.poe.text 19 | 20 | Agreement 21 | =============================================================================== 22 | 23 | See PyPoE/LICENSE 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | from functools import partial 32 | 33 | # 3rd-party 34 | import pytest 35 | 36 | # self 37 | from PyPoE.poe import text 38 | 39 | # ============================================================================= 40 | # Setup 41 | # ============================================================================= 42 | 43 | 44 | def sample_parser(**kwargs): 45 | if kwargs['parameter']: 46 | return '<%(id)s attr="%(parameter)s">%(hstr)s' % kwargs 47 | else: 48 | return '<%(id)s>%(hstr)s' % kwargs 49 | 50 | # ============================================================================= 51 | # Fixtures 52 | # ============================================================================= 53 | 54 | # ============================================================================= 55 | # Tests 56 | # ============================================================================= 57 | 58 | 59 | class TestTags(): 60 | sample_strings = [ 61 | ( 62 | 'Basic', 63 | 'Basic', 64 | [], 65 | ), 66 | # Basic functionality 67 | ( 68 | 'Test {test} part 2 {more test}', 69 | 'Test test part 2 more test', 70 | ['item'], 71 | ), 72 | # nested values 73 | ( 74 | '{{<size:45>{test}}}', 75 | '<bold><title><size attr="45">test</size>', 76 | ['bold', 'title', 'size'], 77 | ), 78 | ( 79 | 'Test <>', 80 | 'Test <>', 81 | [], 82 | ), 83 | ( 84 | 'Format string {0} test', 85 | 'Format string {0} test', 86 | [], 87 | ), 88 | ] 89 | 90 | @pytest.mark.parametrize('input,output,handler_ids', sample_strings) 91 | def test_parsing_results(self, input, output, handler_ids): 92 | handlers = { 93 | hid: partial(sample_parser, id=hid) 94 | for hid in handler_ids 95 | } 96 | 97 | tag = text.parse_description_tags(input) 98 | assert tag.handle_tags(handlers=handlers) == output -------------------------------------------------------------------------------- /scripts/profile/PyPoE/poe/file/dat.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | scripts/profile/PyPoE/poe/file/.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: a7d89455b1d4caae883c4b3a3d255f1167c22e71 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | import os 34 | 35 | # 3rd-party 36 | from line_profiler import LineProfiler 37 | 38 | # self 39 | from PyPoE.poe.file import dat 40 | 41 | # ============================================================================= 42 | # Globals 43 | # ============================================================================= 44 | 45 | __all__ = [] 46 | 47 | dir = 'C:/Temp/' 48 | 49 | # ============================================================================= 50 | # Classes 51 | # ============================================================================= 52 | 53 | # ============================================================================= 54 | # Functions 55 | # ============================================================================= 56 | 57 | def read_dat(file_name='GrantedEffects.dat'): 58 | d = dat.DatFile('GrantedEffects.dat') 59 | d.read(os.path.join(dir, 'Data', file_name)) 60 | return d 61 | 62 | def rr(files=['BaseItemTypes.dat']): 63 | rr = dat.RelationalReader(path_or_file_system=dir, files=files) 64 | 65 | 66 | 67 | # ============================================================================= 68 | # Init 69 | # ============================================================================= 70 | 71 | if __name__ == '__main__': 72 | 73 | profiler = LineProfiler() 74 | #profiler.add_function(dat.DatValue.__init__) 75 | #profiler.add_function(dat.DatReader._cast_from_spec) 76 | #profiler.add_function(dat.DatReader._process_row) 77 | #profiler.add_function(dat.DatRecord.__getitem__) 78 | 79 | #profiler.run("d = read_dat()") 80 | #profiler.run("for i in range(0, 10000): d.reader[0]['Data1']") 81 | 82 | #print(d.reader[0]) 83 | 84 | #profiler.add_function(dat.RelationalReader._set_value) 85 | #profiler.add_function(dat.RelationalReader._dv_set_value) 86 | #profiler.add_function(dat.RelationalReader._simple_set_value) 87 | #profiler.add_function(dat.RelationalReader.read_file) 88 | #profiler.run("rr = dat.RelationalReader(path_or_file_system=dir, files=['Data/BaseItemTypes.dat'], read_options={'use_dat_value': False})") 89 | #profiler.print_stats() 90 | rr = dat.RelationalReader(path_or_file_system=dir, files=['Data/MonsterVarieties.dat'], read_options={'use_dat_value': False}) -------------------------------------------------------------------------------- /docs/source/CLI/config.rst: -------------------------------------------------------------------------------- 1 | Config 2 | ============================================================================== 3 | 4 | Config sub commands are accessible with: 5 | 6 | :command:`config ` 7 | 8 | The config will be located in the user folder. 9 | 10 | Linux: ~/.PyPoE 11 | 12 | Windows: %APPDATA%/PyPoE/ 13 | 14 | Commands 15 | ------------------------------------------------------------------------------ 16 | 17 | .. _cli-config-get: 18 | 19 | config get 20 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 21 | 22 | :command:`config get ` is used to get a config variable. 23 | 24 | :command:`config get -h` provides a list of available variables. 25 | Alternatively it is also possible to use the :ref:`cli-config-print_all` command. 26 | 27 | .. _cli-config-set: 28 | 29 | config set 30 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 31 | 32 | :command:`config set ` is used to get a config variable. 33 | 34 | :command:`config set -h` provides a list of available variables. 35 | Alternatively it is also possible to use the :ref:`cli-config-print_all` 36 | command. 37 | 38 | .. _cli-config-print_all: 39 | 40 | config print_all 41 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 42 | 43 | :command:`config print_all` is used to print a list of the currently 44 | registered config variables and their values. 45 | It will also show missing values. 46 | 47 | For example: 48 | 49 | .. code-block:: none 50 | 51 | 09:11:46 Current stored config variables: 52 | 09:11:46 distributor: DISTRIBUTOR.ALL 53 | 09:11:46 version: VERSION.STABLE 54 | 55 | 09:11:46 Missing config variables (require config set): 56 | 09:11:46 out_dir 57 | 09:11:46 temp_dir 58 | 59 | If you see an output similar to above, it is required to set the variables shown 60 | in the output with :ref:`cli-config-set`. 61 | 62 | For example: 63 | 64 | :command:`config set out_dir C:/Out` 65 | 66 | :command:`config set temp_dir C:/Temp` 67 | 68 | PyPoE Exporter config variables 69 | ------------------------------------------------------------------------------ 70 | 71 | distributor 72 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 73 | 74 | The distributor to use when scanning for a GGPK file. 75 | 76 | A list of valid distributors is located at 77 | :py:class:`PyPoE.poe.constants.DISTRIBUTOR` 78 | 79 | The variable accepts both numeric and literal values, however it is recommended 80 | to use the literal value. 81 | 82 | version 83 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 84 | 85 | The version to use when scanning for a GGPK file. 86 | 87 | A list of valid versions is located at 88 | :py:class:`PyPoE.poe.constants.VERSION` 89 | 90 | The variable accepts both numeric and literal values, however it is recommended 91 | to use the literal value. 92 | 93 | temp_dir 94 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 95 | 96 | The directory to extract temporary files to. This exists in order to speed 97 | up the commandline for certain actions. 98 | 99 | A change to this variable will require :ref:`setup-perform`. 100 | 101 | .. warning:: 102 | The selected directory should have enough space to hold the files. Above 103 | 500MB of free space is recommended. 104 | 105 | out_dir 106 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 107 | 108 | The default directory output files are written to. -------------------------------------------------------------------------------- /PyPoE/ui/shared/table_context_menus.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/ui/shared/table_context_menus.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: df617b1c22194b16dfed3453b06b3a0ccd77af00 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | 34 | # 3rd-party 35 | from PySide2.QtCore import * 36 | from PySide2.QtWidgets import * 37 | 38 | # self 39 | 40 | # ============================================================================= 41 | # Globals 42 | # ============================================================================= 43 | 44 | __all__ = ['TableContextReadOnlyMenu'] 45 | 46 | # ============================================================================= 47 | # Classes 48 | # ============================================================================= 49 | 50 | 51 | class TableContextReadOnlyMenu(QMenu): 52 | def __init__(self, *args, **kwargs): 53 | QMenu.__init__(self, *args, **kwargs) 54 | 55 | qtableview = self.parent() 56 | qtableview.setContextMenuPolicy(Qt.CustomContextMenu) 57 | qtableview.customContextMenuRequested.connect(self.popup) 58 | 59 | self.action_copy = self.addAction("Copy", self.copy) 60 | 61 | self.point = None 62 | 63 | def _handle_data(self, data): 64 | return str(data) 65 | 66 | def copy(self, *args, **kwargs): 67 | qmodelindexlist = self.parent().selectionModel().selectedIndexes() 68 | 69 | rmin, rmax, cmin, cmax = 2**32, 0, 2**32, 0 70 | 71 | for qmodelindex in qmodelindexlist: 72 | rid = qmodelindex.row() 73 | cid = qmodelindex.column() 74 | 75 | rmin = min(rmin, rid) 76 | rmax = max(rmax, rid) 77 | cmin = min(cmin, cid) 78 | cmax = max(cmax, cid) 79 | 80 | # +1 to offset for the minimum size 81 | matrix = [[None for j in range(0, cmax-cmin+1)] for i in range(0, rmax-rmin+1)] 82 | 83 | for qmodelindex in qmodelindexlist: 84 | matrix[qmodelindex.row()-rmin][qmodelindex.column()-cmin] = self._handle_data(qmodelindex.data()) 85 | 86 | # Transform the matrix into a string 87 | out = [] 88 | for row in matrix: 89 | out.append('\t'.join(['' if cell is None else cell for cell in row])) 90 | 91 | QApplication.clipboard().setText('\n'.join(out)) 92 | 93 | def popup(self, point, *args, **kwargs): 94 | self.point = point 95 | QMenu.popup(self, self.parent().mapToGlobal(point)) 96 | 97 | 98 | # ============================================================================= 99 | # Functions 100 | # ============================================================================= 101 | -------------------------------------------------------------------------------- /PyPoE/cli/exporter/core.py: -------------------------------------------------------------------------------- 1 | """ 2 | Exporter Core 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/cli/exporter/core.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 4541268a33ca053438c11d595ef343181852945c $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Exporter main function(s) and entry point. 21 | 22 | The following calls are equivalent: 23 | 24 | .. code-block:: none 25 | 26 | pypoe_exporter 27 | python PyPoE/cli/exporter/core.py 28 | 29 | 30 | Agreement 31 | =============================================================================== 32 | 33 | See PyPoE/LICENSE 34 | 35 | Documentation 36 | =============================================================================== 37 | 38 | .. autofunction:: main 39 | """ 40 | 41 | # ============================================================================= 42 | # Imports 43 | # ============================================================================= 44 | 45 | # Python 46 | import argparse 47 | 48 | # 3rd party 49 | 50 | # self 51 | from PyPoE.shared.config.validator import IntEnumValidator 52 | from PyPoE.poe.constants import VERSION, DISTRIBUTOR 53 | from PyPoE.cli.core import run 54 | from PyPoE.cli.handler import ConfigHandler, SetupHandler 55 | from PyPoE.cli.exporter import config 56 | from PyPoE.cli.exporter.dat import DatHandler 57 | from PyPoE.cli.exporter.wiki.core import WikiHandler 58 | 59 | # ============================================================================= 60 | # Classes 61 | # ============================================================================= 62 | 63 | 64 | # ============================================================================= 65 | # Functions 66 | # ============================================================================= 67 | 68 | 69 | def setup_config(): 70 | config.validator.functions.update({ 71 | 'is_version': IntEnumValidator( 72 | enum=VERSION, 73 | ), 74 | 'is_distributor': IntEnumValidator( 75 | enum=DISTRIBUTOR, 76 | ) 77 | }) 78 | 79 | config.add_option('version', 'is_version(default=%s)' % 80 | VERSION.DEFAULT.value) 81 | config.add_option('distributor', 'is_distributor(default=%s)' % 82 | DISTRIBUTOR.DEFAULT.value) 83 | config.add_option( 84 | 'ggpk_path', 'is_directory(default="", exists=True, allow_empty=True)' 85 | ) 86 | config.add_option('language', 87 | 'option("English", "French", "German", "Portuguese",' 88 | '"Russian", "Spanish", "Thai", "Simplified Chinese",' 89 | '"Traditional Chinese", "Korean", default="English")') 90 | 91 | 92 | def main(): 93 | """ 94 | Entry point for the CLI PyPoE exporter 95 | """ 96 | # Setup 97 | main_parser = argparse.ArgumentParser() 98 | main_sub = main_parser.add_subparsers() 99 | 100 | setup_config() 101 | 102 | DatHandler(main_sub) 103 | WikiHandler(main_sub) 104 | # In that order.. 105 | SetupHandler(main_sub, config) 106 | ConfigHandler(main_sub, config) 107 | 108 | # Execute 109 | run(main_parser, config) 110 | 111 | if __name__ == '__main__': 112 | main() 113 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/test_idt.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for PyPoE.poe.file.idt 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | tests/PyPoE/poe/file/test_idt.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: d4cf58e0459646ad0fc64b68689e6a2f34efe514 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Tests for PyPoE/tests/poe/file/test_idt.py 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | import os 34 | 35 | # 3rd-party 36 | import pytest 37 | from tempfile import TemporaryDirectory 38 | 39 | # self 40 | from PyPoE.poe.file import idt 41 | 42 | # ============================================================================= 43 | # Setup 44 | # ============================================================================= 45 | 46 | cur_dir = os.path.split(os.path.realpath(__file__))[0] 47 | idl_path = os.path.join(cur_dir, '_data', 'test.idt') 48 | 49 | # ============================================================================= 50 | # Fixtures 51 | # ============================================================================= 52 | 53 | data = { 54 | 'version': 1, 55 | 'image': 'Art/2DItems/Test.dds', 56 | 'records': [ 57 | { 58 | 'name': 'Blade', 59 | 'records': [ 60 | {'x': 1, 'y': 1}, 61 | {'x': 2, 'y': 2}, 62 | ], 63 | }, 64 | { 65 | 'name': 'Handle', 66 | 'records': [ 67 | {'x': 1, 'y': 1}, 68 | {'x': 2, 'y': 2}, 69 | {'x': 3, 'y': 3}, 70 | {'x': 4, 'y': 4}, 71 | ], 72 | }, 73 | ], 74 | } 75 | 76 | @pytest.fixture 77 | def idt_file(): 78 | return idt.IDTFile(data) 79 | 80 | 81 | # ============================================================================= 82 | # Tests 83 | # ============================================================================= 84 | 85 | class TestIDLFile: 86 | 87 | def eq(self, idt_file): 88 | assert idt_file.version == data['version'] 89 | assert idt_file.image == data['image'] 90 | for i, tex in enumerate(data['records']): 91 | assert idt_file.records[i].name == tex['name'] 92 | for j, coord in enumerate(tex['records']): 93 | assert idt_file.records[i].records[j].x == coord['x'] 94 | assert idt_file.records[i].records[j].y == coord['y'] 95 | 96 | def test_init(self, idt_file): 97 | self.eq(idt_file) 98 | 99 | def test_read(self, idt_file): 100 | idt_file2 = idt.IDTFile() 101 | idt_file2.read(idl_path) 102 | 103 | self.eq(idt_file) 104 | 105 | def test_write(self, idt_file): 106 | idt_file2 = idt.IDTFile() 107 | 108 | with TemporaryDirectory() as d: 109 | tmp_path = os.path.join(d, 'test_write.idt') 110 | idt_file.write(tmp_path) 111 | idt_file2.read(tmp_path) 112 | 113 | self.eq(idt_file2) 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /PyPoE/ui/launchpad/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Launchpad 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/ui/launchpad/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 7371945edf8b55c65f5aba81965b33395c00a052 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | import sys 34 | 35 | # 3rd-party 36 | from PySide2.QtCore import * 37 | from PySide2.QtWidgets import * 38 | 39 | # self 40 | from PyPoE.ui.shared import main 41 | 42 | # ============================================================================= 43 | # Globals 44 | # ============================================================================= 45 | 46 | __all__ = ['LaunchpadMainWindow', 'launchpad_main'] 47 | 48 | # ============================================================================= 49 | # Classes 50 | # ============================================================================= 51 | 52 | class LaunchpadMainWindow(QMainWindow): 53 | 54 | child_closed = Signal(QWidget) 55 | 56 | def __init__(self, apps, *args, **kwargs): 57 | QMainWindow.__init__(self, *args, **kwargs) 58 | self.apps = apps 59 | 60 | self.child_closed.connect(self._handle_closed_child) 61 | 62 | self.setWindowTitle(self.tr('PyPoE UI Launchpad')) 63 | 64 | frame = QFrame(parent=self) 65 | layout = QVBoxLayout() 66 | frame.setLayout(layout) 67 | 68 | layout.addWidget(QLabel(self.tr('Choose an application to start'))) 69 | 70 | self.buttons = [] 71 | self.instances = [] 72 | for i, qmainwindow_cls in enumerate(apps): 73 | button = QPushButton(qmainwindow_cls.NAME) 74 | button.clicked.connect(self._wrap_clicked(i)) 75 | layout.addWidget(button) 76 | self.buttons.append(button) 77 | self.instances.append(None) 78 | 79 | self.setCentralWidget(frame) 80 | 81 | def _wrap_clicked(self, i): 82 | def wrapped(): 83 | return self.run_application(i) 84 | return wrapped 85 | 86 | def _handle_closed_child(self, qwidget): 87 | for i, item in enumerate(self.instances): 88 | if item == qwidget: 89 | break 90 | 91 | self.buttons[i].setEnabled(True) 92 | self.buttons[i].setText(qwidget.NAME) 93 | 94 | def run_application(self, i): 95 | self.buttons[i].setEnabled(False) 96 | self.buttons[i].setText(self.buttons[i].text() + self.tr(' (Running)')) 97 | qmainwindow = self.apps[i](parent=self) 98 | qmainwindow.show() 99 | qmainwindow.activateWindow() 100 | 101 | self.instances[i] = qmainwindow 102 | 103 | # ============================================================================= 104 | # Functions 105 | # ============================================================================= 106 | 107 | 108 | def launchpad_main(*args, **kwargs): 109 | main(LaunchpadMainWindow, *args, **kwargs) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Development is currently discontinued** 2 | 3 | PyPoE 4 | ======== 5 | 6 | Collection of Python Tools for [Path of Exile](https://www.pathofexile.com/). 7 | 8 | More detailed docs: [http://omegak2.net/poe/PyPoE/](http://omegak2.net/poe/PyPoE/) 9 | 10 | The docs are occasionally updated until I get a build bot up - however docs can also be manually built with Sphinx. 11 | 12 | [![Build Status](https://travis-ci.org/OmegaK2/PyPoE.svg?branch=dev)](https://travis-ci.org/OmegaK2/PyPoE) 13 | 14 | Common Problems & Advisory 15 | -------- 16 | * Install **Python 3.7** for maximum compatibility: 17 | * To support bundle decompression check out https://github.com/zao/ooz, compile it and place libooz.dll in the python directory 18 | * **UI will be reworked for bundle support and is not functional at the moment** 19 | * On Windows 10 machines there seems to a be bug in the Python installation that prevents arguments being passed to the command line interface; you can identify this issue if you get a "help" listing if you supplied more then 1 argument. See [this on stack overflow](https://stackoverflow.com/questions/2640971/windows-is-not-passing-command-line-arguments-to-python-programs-executed-from-t) for possible solutions 20 | 21 | 22 | Overview 23 | -------- 24 | Parts: 25 | * Library toolkit for programmers (PyPoE/poe) 26 | * UI based on Qt for browsing the game files 27 | * CLI interface for extracting/exporting data (for the wiki, more TBD) 28 | 29 | Resources 30 | ------- 31 | * IRC Channel: [freenode.net/#PyPoE](http://webchat.freenode.net/?channels=#PyPoE) 32 | * Discord: No official channel, but I can be contacted in #3rd-party-tool-dev in the /r/PathOfExile Discord 33 | 34 | Important Notes 35 | -------- 36 | Alpha Stage: 37 | * Code structure and in particular the API may change at any time 38 | * Incomplete in many areas (check files and TODOs) 39 | * Tests still have to be written for a lot of things. 40 | * Many functions and classes are not yet fully documented 41 | 42 | Dev branch: 43 | * Broken code may be committed occasionally to the dev branch 44 | 45 | Quick Setup Guide 46 | -------- 47 | These instructions are for the current development version of PyPoE. 48 | 49 | * Install Python 3.7 & git 50 | * On Windows, make sure Python 3.7 and Python "Scripts" folder are in %PATH% 51 | * Checkout PyPoE with git 52 | * Go into the PyPoE folder 53 | * Minimum install: ```pip3 install -e . ``` 54 | * Full install: ```pip3 install -e .[full]``` 55 | * Download and compile https://github.com/zao/ooz with cmake 56 | * Place the resulting libooz.dll in the python folder 57 | 58 | Usage 59 | -------- 60 | * UI: ```pypoe_ui``` 61 | * CLI: ```pypoe_exporter``` (follow the instructions) 62 | * API: check the individual files in PyPoE/poe/ or the docs [http://omegak2.net/poe/PyPoE/](http://omegak2.net/poe/PyPoE/) 63 | 64 | Credits - People 65 | -------- 66 | * [Grinding Gear Games](http://www.grindinggear.com/) - they created many of the file formats and [Path of Exile](https://www.pathofexile.com/) obviously, so do not reuse their files anywhere without their permission and support them if you are able to :) 67 | * [Chriskang](http://pathofexile.gamepedia.com/User:Chriskang) and the original [VisualGGPK2](http://pathofexile.gamepedia.com/User:Chriskang/VisualGGPK2) 68 | * [chuanhsing](https://www.reddit.com/u/chuanhsing) ([poedb](http://poedb.tw/us/index.php)) for helping with meaning of certain specification values and retrieving monster stats 69 | 70 | Credits - Libraries 71 | ------- 72 | * [pyside2](https://wiki.qt.io/Qt_for_Python) ([pypi](https://pypi.org/project/PySide2/)) 73 | * [configobj](http://www.voidspace.org.uk/python/configobj.html) ([pypi](https://pypi.org/project/configobj/)) 74 | * colorama ([pypi](https://pypi.org/project/colorama/)) 75 | * sphinx ([pypi](https://pypi.org/project/sphinx/)) 76 | * pytest ([pypi](https://pypi.org/project/pytest/)) 77 | * PyOpenGL ([pypi](https://pypi.org/project/PyOpenGL/)) 78 | * tqdm ([pypi](https://pypi.org/project/tqdm/)) 79 | * graphviz ([pypi](https://pypi.org/project/graphviz/)) 80 | * mwclient ([pypi](https://pypi.org/project/mwclient/)) 81 | * mwclientparserfromhell ([pypi](https://pypi.org/project/mwparserfromhell/)) 82 | * rapidfuzz ([pypi](https://pypi.org/project/rapidfuzz/)) 83 | -------------------------------------------------------------------------------- /PyPoE/poe/file/specification/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | =============================================================================== 4 | 5 | +----------+------------------------------------------------------------------+ 6 | | Path | PyPoE/poe/file/specification/__init__.py | 7 | +----------+------------------------------------------------------------------+ 8 | | Version | 1.0.0a0 | 9 | +----------+------------------------------------------------------------------+ 10 | | Revision | $Id: bab0874388e86a946efc474d7aa1c72d551325e6 $ | 11 | +----------+------------------------------------------------------------------+ 12 | | Author | Omega_K2 | 13 | +----------+------------------------------------------------------------------+ 14 | 15 | .. autofunction:: load 16 | """ 17 | 18 | # ============================================================================= 19 | # Imports 20 | # ============================================================================= 21 | 22 | # Python 23 | import importlib 24 | from importlib.machinery import SourceFileLoader 25 | 26 | # 3rd-party 27 | 28 | # self 29 | from PyPoE.poe.constants import VERSION 30 | 31 | # ============================================================================= 32 | # Globals 33 | # ============================================================================= 34 | 35 | __all__ = ['load'] 36 | 37 | # ============================================================================= 38 | # Globals 39 | # ============================================================================= 40 | 41 | 42 | def load(path=None, version=VERSION.DEFAULT, reload=False, validate=None): 43 | """ 44 | Loads a specification from a python module that can be used for the dat 45 | files. 46 | The file must implement the classes from 47 | :py:mod:`PyPoE.poe.file.specification.fields` and expose the specification 48 | with a variable "specification" for this to work properly. 49 | 50 | Since this function is using python imports specifications are 51 | automatically cached once loaded. If using a cached version is not desired 52 | set the reload parameter to True. 53 | 54 | .. warning:: 55 | Please note that many usages of the reload function will cause a memory 56 | leak since python does not remove old modules from it's cache. 57 | 58 | Parameters 59 | ---------- 60 | path : str 61 | If specified, read the specified python module as specification 62 | version : constants.VERSION 63 | Version of the game to load the specification for; only works if 64 | path is not specified. 65 | reload : bool 66 | Whether to reload the specified specification. 67 | validate : bool or None 68 | Whether additional validation will be run on the Specification. 69 | By default (None), this will only occur when custom specifications are 70 | loaded and not when default specifications are loaded. 71 | 72 | Returns 73 | ------- 74 | :class:`ConfigObj` 75 | returns the ConfigObj of the read file. 76 | 77 | 78 | Raises 79 | ------ 80 | ValueError 81 | if version passed is not valid 82 | SpecificationError 83 | if validation is enabled and any issues occur 84 | """ 85 | if path is None: 86 | if validate is None: 87 | validate = False 88 | 89 | if version in (VERSION.STABLE, VERSION.BETA, VERSION.ALPHA): 90 | module = importlib.import_module( 91 | 'PyPoE.poe.file.specification.data.%s' % 92 | str(version).split('.')[1].lower() 93 | ) 94 | else: 95 | raise ValueError( 96 | 'Unknown version or version currently not supported: %s' % 97 | version 98 | ) 99 | else: 100 | if validate is None: 101 | validate = True 102 | 103 | module = SourceFileLoader('', path).load_module() 104 | 105 | if reload: 106 | importlib.reload(module) 107 | 108 | if validate: 109 | module.specification.validate() 110 | 111 | return module.specification 112 | -------------------------------------------------------------------------------- /PyPoE/ui/shared/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shared UI code 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/ui/shared/__init__.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 36e6c6c8023888acc48e6b8ad6fdcf5b2d67c099 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | import sys 34 | import os 35 | import time 36 | 37 | # 3rd-party 38 | from PySide2.QtCore import * 39 | from PySide2.QtWidgets import * 40 | 41 | # self 42 | from PyPoE.ui.shared.settings import SettingsWindow 43 | 44 | # ============================================================================= 45 | # Globals 46 | # ============================================================================= 47 | 48 | __all__ = ['SharedMainWindow', 'main'] 49 | 50 | # ============================================================================= 51 | # Classes 52 | # ============================================================================= 53 | 54 | 55 | class SharedMainWindow(QMainWindow): 56 | """ 57 | Shared Window class for use in sub applications to be launched from the 58 | Launchpad. 59 | """ 60 | 61 | sig_log_message = Signal(str) 62 | 63 | def __init__(self, *args, app_name=NotImplemented, **kwargs): 64 | super().__init__(*args, **kwargs) 65 | 66 | self.NAME = app_name 67 | 68 | QCoreApplication.setApplicationName(app_name) 69 | QSettings.setDefaultFormat(QSettings.IniFormat) 70 | self.settings = QSettings() 71 | self.APP_ROOT_DIR = os.path.split(self.settings.fileName())[0] 72 | self.APP_DIR = os.path.join(self.APP_ROOT_DIR, app_name) 73 | if not os.path.exists(self.APP_DIR): 74 | os.makedirs(self.APP_DIR) 75 | 76 | # Setup logging 77 | self.sig_log_message.connect(self._write_log) 78 | 79 | # Still needs to be setup 80 | self.notification = QTextEdit(readOnly=True) 81 | self.notification.setFixedHeight(100) 82 | 83 | self.settings_window = SettingsWindow(parent=self) 84 | 85 | def _write_log(self, msg, notification=None): 86 | timef = time.strftime("%H:%M:%S - ") 87 | if notification is None: 88 | notification = msg 89 | self.statusBar().showMessage(timef + notification) 90 | self.notification.append(timef + msg) 91 | QApplication.instance().processEvents() 92 | 93 | def closeEvent(self, *args, **kwargs): 94 | p = self.parent() 95 | if p is None: 96 | return 97 | 98 | p.child_closed.emit(self) 99 | 100 | 101 | # ============================================================================= 102 | # Functions 103 | # ============================================================================= 104 | 105 | 106 | def main(maincls, *args, **kwargs): 107 | """ 108 | Load translations/app and start the qt application. 109 | 110 | Parameters 111 | ---------- 112 | maincls 113 | Class to instantiate with the given arguments and keywords 114 | """ 115 | translator = QTranslator() 116 | translator.load('i18n/en_US') 117 | 118 | app = QApplication(sys.argv) 119 | app.setOrganizationName('PyPoE') 120 | app.installTranslator(translator) 121 | 122 | maininst = maincls(*args, **kwargs) 123 | maininst.show() 124 | 125 | sys.exit(app.exec_()) 126 | -------------------------------------------------------------------------------- /tests/test_data.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for _data/dat.specification.ini 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | tests/test_data.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: f495e4cca390ac2f31c073b67c40b61e723f514a $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Tests for for the specifications found in dat.specification.ini. 21 | Running this test is relatively time-consuming, so it may be a good idea to 22 | avoid it unless a PoE update has been released to locate broken or unsupported 23 | .dat files. 24 | 25 | Agreement 26 | =============================================================================== 27 | 28 | See PyPoE/LICENSE 29 | """ 30 | 31 | # ============================================================================= 32 | # Imports 33 | # ============================================================================= 34 | 35 | # Python 36 | 37 | # 3rd Party 38 | import pytest 39 | 40 | # self 41 | from PyPoE.poe.file import dat 42 | from PyPoE.poe.file.specification import load 43 | 44 | # ============================================================================= 45 | # Functions 46 | # ============================================================================= 47 | 48 | 49 | # ============================================================================= 50 | # Setup 51 | # ============================================================================= 52 | 53 | 54 | @pytest.fixture(scope='module') 55 | def files(poe_version): 56 | return [ 57 | section for section in load(version=poe_version) 58 | ] 59 | 60 | # ============================================================================= 61 | # Tests 62 | # ============================================================================= 63 | 64 | # Kind of testing the reading of the files twice, but whatever. 65 | # dat_file_name is parametrized in conftest.py 66 | @pytest.mark.parametrize('x64', (False, )) 67 | def test_definitions(dat_file_name, file_system, x64): 68 | opt = { 69 | 'use_dat_value': False, 70 | 'x64': x64 71 | } 72 | if x64: 73 | dat_file_name += '64' 74 | # Will raise errors accordingly if it fails 75 | df = dat.DatFile(dat_file_name) 76 | try: 77 | df.read(file_system.get_file('Data/' + dat_file_name), **opt) 78 | # If a file is in the spec, but not in the dat file this is allright 79 | except FileNotFoundError: 80 | return 81 | 82 | 83 | def test_missing(files, file_system): 84 | file_set = set() 85 | 86 | for fn in file_system.index.get_dir_record('Data/').files: 87 | if not fn.endswith('.dat'): 88 | continue 89 | 90 | # Not a regular dat file, ignore 91 | if fn in ['Languages.dat']: 92 | continue 93 | 94 | file_set.add(fn) 95 | 96 | # Sorting by name makes this easier to correct when error shows up 97 | assert sorted(file_set.difference(set(files))) == [], 'ggpk contains unhandled .dat files' 98 | assert sorted(set(files).difference(file_set)) == [], 'dat specification contains unused dat files' 99 | 100 | 101 | # unique_dat_file_name & unique_field_name are parametrized in conftest.py 102 | def test_uniqueness(unique_dat_file_name, unique_dat_field_name, rr): 103 | df = rr[unique_dat_file_name] 104 | index = df.table_columns[unique_dat_field_name]['index'] 105 | 106 | data = [] 107 | for row in df: 108 | value = row[index] if not isinstance(row[index], dat.DatRecord) else row[index].rowid 109 | # Duplicate "None" values are acceptable. 110 | if value is None: 111 | continue 112 | data.append(value) 113 | 114 | assert len(data) == len(set(data)) 115 | 116 | -------------------------------------------------------------------------------- /PyPoE/poe/file/ot.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | =============================================================================== 4 | 5 | +----------+------------------------------------------------------------------+ 6 | | Path | PyPoE/poe/file/ot.py | 7 | +----------+------------------------------------------------------------------+ 8 | | Version | 1.0.0a0 | 9 | +----------+------------------------------------------------------------------+ 10 | | Revision | $Id: c9fb2948d920d5ba2332843491c65bcac32a15d3 $ | 11 | +----------+------------------------------------------------------------------+ 12 | | Author | Omega_K2 | 13 | +----------+------------------------------------------------------------------+ 14 | 15 | Description 16 | =============================================================================== 17 | 18 | Support for .ot file format. 19 | 20 | .ot file seem to be generally used for server-side settings related to abstract 21 | objects. 22 | 23 | Generally make sure to consider the context of the file when interpreting the 24 | contents; there is a chance they're extended or embedded though .dat files and 25 | the key/value pairs found are in relevance to the context. 26 | 27 | Usually they're accompanied by .otc files which handle client-side settings. 28 | 29 | See also: 30 | 31 | * :mod:`PyPoE.poe.file.otc` 32 | * :mod:`PyPoE.poe.file.dat` 33 | 34 | Agreement 35 | =============================================================================== 36 | 37 | See PyPoE/LICENSE 38 | 39 | 40 | Documentation 41 | =============================================================================== 42 | 43 | .. autoclass:: OTFile 44 | :exclude-members: clear, copy, default_factory, fromkeys, get, items, keys, pop, popitem, setdefault, update, values 45 | 46 | .. autoclass:: OTFileCache 47 | 48 | """ 49 | 50 | # ============================================================================= 51 | # Imports 52 | # ============================================================================= 53 | 54 | # Python 55 | 56 | # 3rd-party 57 | 58 | # self 59 | from PyPoE.shared.decorators import doc 60 | from PyPoE.poe.file.shared.keyvalues import * 61 | 62 | # ============================================================================= 63 | # Globals 64 | # ============================================================================= 65 | 66 | __all__ = ['OTFile', 'OTFileCache'] 67 | 68 | # ============================================================================= 69 | # Classes 70 | # ============================================================================= 71 | 72 | 73 | class ActionKeyValueSection(AbstractKeyValueSection): 74 | NAME = 'Actor' 75 | 76 | 77 | class AnimatedKeyValueSection(AbstractKeyValueSection): 78 | NAME = 'Animated' 79 | 80 | 81 | class BaseKeyValueSection(AbstractKeyValueSection): 82 | NAME = 'Base' 83 | ORDERED_HASH_KEYS = {'tag'} 84 | 85 | 86 | class ModsKeyValueSection(AbstractKeyValueSection): 87 | NAME = 'Mods' 88 | ORDERED_HASH_KEYS = {'enable_rarity'} 89 | 90 | 91 | class PathfindingKeyValueSection(AbstractKeyValueSection): 92 | NAME = 'Pathfinding' 93 | 94 | 95 | class PositionedKeyValueSection(AbstractKeyValueSection): 96 | NAME = 'Positioned' 97 | 98 | 99 | class SocketsKeyValueSection(AbstractKeyValueSection): 100 | NAME = 'Sockets' 101 | 102 | 103 | class StatsKeyValueSection(AbstractKeyValueSection): 104 | NAME = 'Stats' 105 | 106 | 107 | @doc(append=AbstractKeyValueFile) 108 | class OTFile(AbstractKeyValueFile): 109 | """ 110 | Representation of a .ot file. 111 | """ 112 | 113 | SECTIONS = dict((s.NAME, s) for s in [ 114 | ActionKeyValueSection, 115 | AnimatedKeyValueSection, 116 | BaseKeyValueSection, 117 | ModsKeyValueSection, 118 | PathfindingKeyValueSection, 119 | PositionedKeyValueSection, 120 | SocketsKeyValueSection, 121 | StatsKeyValueSection, 122 | ]) 123 | 124 | EXTENSION = '.ot' 125 | 126 | def __init__(self, *args, **kwargs): 127 | super().__init__(*args, **kwargs) 128 | 129 | 130 | @doc(append=AbstractKeyValueFileCache) 131 | class OTFileCache(AbstractKeyValueFileCache): 132 | """ 133 | Cache for OTFile instances. 134 | """ 135 | FILE_TYPE = OTFile 136 | -------------------------------------------------------------------------------- /tests/PyPoE/shared/config/test_validator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | =============================================================================== 4 | 5 | +----------+------------------------------------------------------------------+ 6 | | Path | test_validator.py | 7 | +----------+------------------------------------------------------------------+ 8 | | Version | 1.0.0a0 | 9 | +----------+------------------------------------------------------------------+ 10 | | Revision | $Id: ea6f6a164de99f56d2b26148be0cede30f7498f8 $ | 11 | +----------+------------------------------------------------------------------+ 12 | | Author | Omega_K2 | 13 | +----------+------------------------------------------------------------------+ 14 | 15 | Description 16 | =============================================================================== 17 | 18 | 19 | 20 | Agreement 21 | =============================================================================== 22 | 23 | See PyPoE/LICENSE 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | import os 32 | from enum import IntEnum 33 | 34 | # 3rd-party 35 | import pytest 36 | from validate import ValidateError 37 | 38 | # self 39 | from PyPoE.shared.config import validator 40 | 41 | # ============================================================================= 42 | # Setup 43 | # ============================================================================= 44 | 45 | 46 | class MyIntEnum(IntEnum): 47 | a = 1 48 | b = 2 49 | c = 3 50 | 51 | # ============================================================================= 52 | # Fixtures 53 | # ============================================================================= 54 | 55 | 56 | @pytest.fixture(scope='module') 57 | def int_enum_tester(): 58 | return validator.IntEnumValidator(enum=MyIntEnum, default=MyIntEnum.a) 59 | 60 | 61 | @pytest.fixture(scope='module') 62 | def file_path(): 63 | return __file__ 64 | 65 | 66 | @pytest.fixture(scope='module') 67 | def dir_path(): 68 | return os.path.split(__file__)[0] 69 | 70 | 71 | # ============================================================================= 72 | # Tests 73 | # ============================================================================= 74 | 75 | 76 | class TestIntEnumValidator: 77 | values = ( 78 | (1, MyIntEnum.a), 79 | ('a', MyIntEnum.a), 80 | ('1', MyIntEnum.a), 81 | # Will be written as this value into config 82 | ('MyIntEnum.a', MyIntEnum.a), 83 | ) 84 | 85 | fvalues = ( 86 | ('4', '4 (str) is out of range and should fail'), 87 | ('2.0', '2.0 (str) is not a valid integer'), 88 | (4, '4 (int) is out of range and should fail'), 89 | (2.0, '2.0 (float) is not a valid integer'), 90 | ) 91 | 92 | 93 | def test_init(self, int_enum_tester): 94 | with pytest.raises(TypeError): 95 | validator.IntEnumValidator(enum=int) 96 | validator.IntEnumValidator(enum=MyIntEnum, default=5) 97 | validator.IntEnumValidator(enum=MyIntEnum, default='c') 98 | validator.IntEnumValidator(enum=MyIntEnum, default=int) 99 | 100 | @pytest.mark.parametrize('value,expected', values) 101 | def test_success(self, int_enum_tester, value, expected): 102 | assert int_enum_tester(value) == expected 103 | 104 | @pytest.mark.parametrize('value,errmsg', fvalues) 105 | def test_fail(self, int_enum_tester, value, errmsg): 106 | with pytest.raises(ValidateError): 107 | int_enum_tester(value) 108 | 109 | 110 | class TestIsFile: 111 | def test_success(self, file_path): 112 | assert validator.is_file(file_path) == file_path 113 | 114 | @pytest.mark.xfail(ValidateError) 115 | def test_fail(self, dir_path): 116 | validator.is_file(dir_path) 117 | 118 | 119 | class TestIsDirectory: 120 | def test_success(self, dir_path): 121 | assert validator.is_directory(dir_path) == dir_path 122 | 123 | @pytest.mark.xfail(ValidateError) 124 | def test_fail(self, file_path): 125 | validator.is_directory(file_path) 126 | -------------------------------------------------------------------------------- /scripts/make_empty_spec.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | =============================================================================== 4 | 5 | +----------+------------------------------------------------------------------+ 6 | | Path | scripts/make_empty_spec.py | 7 | +----------+------------------------------------------------------------------+ 8 | | Version | 1.0.0a0 | 9 | +----------+------------------------------------------------------------------+ 10 | | Revision | $Id: 82618bfed39864057c3983fac3656bce2c065cf1 $ | 11 | +----------+------------------------------------------------------------------+ 12 | | Author | Omega_K2 | 13 | +----------+------------------------------------------------------------------+ 14 | 15 | Description 16 | =============================================================================== 17 | 18 | 19 | 20 | Agreement 21 | =============================================================================== 22 | 23 | See PyPoE/LICENSE 24 | 25 | Documentation 26 | =============================================================================== 27 | 28 | Public API 29 | ------------------------------------------------------------------------------- 30 | 31 | Internal API 32 | ------------------------------------------------------------------------------- 33 | """ 34 | 35 | # ============================================================================= 36 | # Imports 37 | # ============================================================================= 38 | 39 | # Python 40 | import os 41 | import struct 42 | 43 | # 3rd-party 44 | 45 | # self 46 | from PyPoE.poe.constants import VERSION, DISTRIBUTOR 47 | from PyPoE.poe.path import PoEPath 48 | from PyPoE.poe.file import dat 49 | from PyPoE.poe.file.ggpk import GGPKFile 50 | from PyPoE.poe.file.bundle import Index, Bundle 51 | 52 | # ============================================================================= 53 | # Globals 54 | # ============================================================================= 55 | 56 | __all__ = [] 57 | 58 | # ============================================================================= 59 | # Classes 60 | # ============================================================================= 61 | 62 | # ============================================================================= 63 | # Functions 64 | # ============================================================================= 65 | 66 | 67 | def spec_unknown(size, i=0): 68 | if size == 0: 69 | return '' 70 | spec = "Field(" 71 | out = [] 72 | while size >= 4: 73 | out.append(spec) 74 | out.append(" name='Unknown%s'," % i) 75 | out.append(" type='int',") 76 | out.append("),") 77 | size -= 4 78 | i+=1 79 | 80 | mod = size % 4 81 | for j in range(0, mod): 82 | out.append(spec) 83 | out.append(" name='Unknown%s'," % i) 84 | out.append(" type='byte',") 85 | out.append("),") 86 | i+=1 87 | 88 | return ' '*12 + ('\n' + ' '*12).join(out) 89 | 90 | 91 | def run(): 92 | out = [] 93 | 94 | path = PoEPath(version=VERSION.STABLE, distributor=DISTRIBUTOR.GGG 95 | ).get_installation_paths()[0] 96 | dat.set_default_spec(VERSION.STABLE) 97 | existing_set = set(dat._default_spec.keys()) 98 | 99 | ggpk = GGPKFile() 100 | ggpk.read(os.path.join(path, 'content.ggpk')) 101 | ggpk.directory_build() 102 | 103 | index = Index() 104 | index.read(ggpk[Index.PATH].record.extract()) 105 | 106 | file_set = set() 107 | 108 | for name in index.get_dir_record('Data').files: 109 | if not name.endswith('.dat'): 110 | continue 111 | 112 | # Not a regular dat file, ignore 113 | if name in ['Languages.dat']: 114 | continue 115 | 116 | file_set.add(name) 117 | 118 | new = sorted(file_set.difference(set(existing_set))) 119 | 120 | for fn in new: 121 | fr = index.get_file_record('Data/' + fn) 122 | fr.bundle.read(ggpk[fr.bundle.ggpk_path].record.extract()) 123 | binary = fr.get_file() 124 | data_offset = binary.find(dat.DAT_FILE_MAGIC_NUMBER) 125 | n_rows = struct.unpack(' 0: 128 | record_length = length//n_rows 129 | 130 | out.append(""" '%s': File( 131 | fields=( 132 | %s 133 | ), 134 | ),""" % (fn, spec_unknown(record_length))) 135 | 136 | print('\n'.join(out)) 137 | 138 | 139 | if __name__ == '__main__': 140 | run() -------------------------------------------------------------------------------- /PyPoE/ui/shared/dialog.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shared Dialog Prompts 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/ui/shared/dialog.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 59f1de5e14e327fa1a7448a2a7c65bfb764f1de6 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Contains various self-contained dialog prompts for various tasks. 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | import re 34 | 35 | # 3rd Party 36 | from PySide2.QtCore import * 37 | from PySide2.QtWidgets import * 38 | 39 | # self 40 | from PyPoE.ui.shared.regex_widgets import RegexFlagsBox 41 | 42 | # ============================================================================= 43 | # Imports 44 | # ============================================================================= 45 | 46 | __all__ = ['RegExSearchDialog'] 47 | 48 | # ============================================================================= 49 | # Imports 50 | # ============================================================================= 51 | 52 | class RegExSearchDialog(QDialog): 53 | def __init__(self, *args, **kwargs): 54 | QDialog.__init__(self, *args, **kwargs) 55 | 56 | self.setWindowTitle(self.tr('RegEx Search Dialog')) 57 | 58 | self.master_layout = QVBoxLayout() 59 | self.setLayout(self.master_layout) 60 | 61 | self.regex_box = RegexFlagsBox(default_flags=re.IGNORECASE) 62 | self.master_layout.addWidget(self.regex_box) 63 | 64 | self.option_group_box = QGroupBox(self.tr('Search Options', parent=self)) 65 | self.master_layout.addWidget(self.option_group_box) 66 | 67 | self.option_group_box_layout = QVBoxLayout() 68 | self.option_group_box.setLayout(self.option_group_box_layout) 69 | 70 | self.option_search_directories = QCheckBox(self.tr('Search directory names', parent=self.option_group_box)) 71 | self.option_group_box_layout.addWidget(self.option_search_directories) 72 | 73 | self.option_full_path = QCheckBox(self.tr('Show full path', parent=self.option_group_box)) 74 | self.option_group_box_layout.addWidget(self.option_full_path) 75 | 76 | self.master_layout.addWidget(QLabel(self.tr('Enter Regular Expression:'), parent=self)) 77 | 78 | self.regex_input = QLineEdit() 79 | self.master_layout.addWidget(self.regex_input) 80 | 81 | self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel | QDialogButtonBox.Reset) 82 | self.button_box.accepted.connect(self.accept) 83 | self.button_box.rejected.connect(self.reject) 84 | 85 | b = self.button_box.button(QDialogButtonBox.Reset) 86 | b.clicked.connect(self.reset) 87 | 88 | self.master_layout.addWidget(self.button_box) 89 | 90 | # Set default states 91 | self.reset() 92 | 93 | def accept(self, *args, **kwargs): 94 | # Validate and store the regex 95 | try: 96 | self.regex_compiled = re.compile(self.regex_input.text(), self.regex_box.get_flags()) 97 | except re.error as e: 98 | QMessageBox.critical(self, self.tr('RegEx Error'), self.tr('regular Expression error:\n %s') % e.args[0]) 99 | # TODO: This may be unncessary. Actually should accept even return rejected? No idea. 100 | return QDialog.Rejected 101 | 102 | return QDialog.accept(self, *args, **kwargs) 103 | 104 | def reset(self): 105 | """ 106 | Set defaults and empty the input text 107 | """ 108 | self.regex_compiled = re.compile('') 109 | self.regex_input.setText('') 110 | self.regex_box.set_defaults() 111 | self.option_search_directories.setCheckState(Qt.CheckState.Unchecked) 112 | self.option_full_path.setCheckState(Qt.CheckState.Checked) -------------------------------------------------------------------------------- /PyPoE/poe/file/specification/errors.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | =============================================================================== 4 | 5 | +----------+------------------------------------------------------------------+ 6 | | Path | PyPoE/poe/file/specification/errors.py | 7 | +----------+------------------------------------------------------------------+ 8 | | Version | 1.0.0a0 | 9 | +----------+------------------------------------------------------------------+ 10 | | Revision | $Id: 7486e2edb854f745b91c67aaa9ff648c4817462a $ | 11 | +----------+------------------------------------------------------------------+ 12 | | Author | Omega_K2 | 13 | +----------+------------------------------------------------------------------+ 14 | 15 | Description 16 | =============================================================================== 17 | 18 | 19 | 20 | Agreement 21 | =============================================================================== 22 | 23 | See PyPoE/LICENSE 24 | 25 | Documentation 26 | =============================================================================== 27 | 28 | .. autoclass:: SpecificationError 29 | 30 | .. autoclass:: SpecificationError.ERRORS 31 | 32 | .. autoclass:: SpecificationWarning 33 | """ 34 | 35 | # ============================================================================= 36 | # Imports 37 | # ============================================================================= 38 | 39 | # Python 40 | from enum import IntEnum 41 | 42 | # 3rd-party 43 | 44 | # self 45 | 46 | # ============================================================================= 47 | # Globals 48 | # ============================================================================= 49 | 50 | __all__ = ['SpecificationError', 'SpecificationWarning'] 51 | 52 | # ============================================================================= 53 | # Exceptions & Warnings 54 | # ============================================================================= 55 | 56 | 57 | class SpecificationError(ValueError): 58 | """ 59 | SpecificationErrors are raised to indicate there is a problem with the 60 | specification compared to the data. 61 | 62 | Unlike most errors, they are raised with an error code and the error 63 | message. The error code can be used to capture specific errors more 64 | accurately. 65 | """ 66 | 67 | class ERRORS(IntEnum): 68 | """ 69 | Numeric meaning: 70 | 71 | * 1xxx - indicates issues with format of fields 72 | * 2xxx - indicates issues with format of virtual fields 73 | * 3xxx - indicates issues at runtime 74 | 75 | Attributes 76 | ---------- 77 | INVALID_FOREIGN_KEY_FILE 78 | Foreign key file does not exist 79 | INVALID_FOREIGN_KEY_ID 80 | Foreign key with the specified id does not exist 81 | INVALID_ARGUMENT_COMBINATION 82 | Invalid combination of multiple arguments; i.e. when they can't be 83 | used together 84 | INVALID_ENUM_NAME 85 | Enum does not exist in :py:mod:`PyPoE.poe.constants` 86 | VIRTUAL_KEY_EMPTY 87 | Virtual key does not have fields defined 88 | VIRTUAL_KEY_DUPLICATE 89 | Virtual key is a duplicate of a regular key 90 | VIRTUAL_KEY_INVALID_KEY 91 | Invalid fields specified for the virtual key 92 | VIRTUAL_KEY_INVALID_DATA_TYPE 93 | Invalid data type(s) in the target fields 94 | RUNTIME_MISSING_SPECIFICATION 95 | No specification found in the specification format used for the 96 | function call 97 | RUNTIME_MISSING_FOREIGN_KEY 98 | A single foreign key reference could not be resolved 99 | RUNTIME_ROWSIZE_MISMATCH 100 | The row size in the specification doesn't match the real data row 101 | size 102 | """ 103 | INVALID_FOREIGN_KEY_FILE = 1000 104 | INVALID_FOREIGN_KEY_ID = 1001 105 | INVALID_ARGUMENT_COMBINATION = 1002 106 | INVALID_ENUM_NAME = 1003 107 | VIRTUAL_KEY_EMPTY = 2000 108 | VIRTUAL_KEY_DUPLICATE = 2001 109 | VIRTUAL_KEY_INVALID_KEY = 2002 110 | VIRTUAL_KEY_INVALID_DATA_TYPE = 2003 111 | RUNTIME_MISSING_SPECIFICATION = 3000 112 | RUNTIME_MISSING_FOREIGN_KEY = 3001 113 | RUNTIME_ROWSIZE_MISMATCH = 3002 114 | 115 | def __init__(self, code, msg): 116 | super().__init__() 117 | self.code = self.ERRORS(code) 118 | self.msg = msg 119 | 120 | def __str__(self): 121 | return '%s: %s' % (repr(self.code), self.msg) 122 | 123 | 124 | class SpecificationWarning(UserWarning): 125 | pass -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """A setuptools based setup module. 2 | 3 | See: 4 | https://packaging.python.org/en/latest/distributing.html 5 | https://github.com/pypa/sampleproject 6 | """ 7 | 8 | # Always prefer setuptools over distutils 9 | from setuptools import setup, find_packages 10 | # To use a consistent encoding 11 | from os import path 12 | 13 | here = path.abspath(path.dirname(__file__)) 14 | 15 | extras_require = { 16 | 'dev': ['sphinx', 'pytest'], 17 | 'cli': ['colorama', 'graphviz', 'tqdm', 'mwclient', 'mwparserfromhell', 'rapidfuzz'], 18 | 'ui': ['pyside2==5.14.0'], 19 | 'ui-extra': ['PyOpenGL'], 20 | } 21 | 22 | _full = {'full': set(), 'cli-full': set(), 'ui-full': set()} 23 | 24 | for k, v in dict(extras_require).items(): 25 | for item in v: 26 | _full['full'].add(item) 27 | if k.startswith('cli'): 28 | _full['cli-full'].add(item) 29 | if k.startswith('ui'): 30 | _full['ui-full'].add(item) 31 | 32 | for k, v in _full.items(): 33 | extras_require[k] = list(v) 34 | 35 | setup( 36 | name='PyPoE', 37 | 38 | # Versions should comply with PEP440. For a discussion on single-sourcing 39 | # the version across setup.py and the project code, see 40 | # https://packaging.python.org/en/latest/single_source_version.html 41 | version='1.0.0a0', 42 | 43 | description='Python Tools for Path of Exile', 44 | long_description="""""", 45 | 46 | # The project's main homepage. 47 | url='https://github.com/OmegaK2/PyPoE', 48 | 49 | # Author details 50 | author='[#OMEGA] - K2', 51 | author_email='omegak2@gmx.de', 52 | 53 | # Choose your license 54 | license='MIT', 55 | 56 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 57 | classifiers=[ 58 | # How mature is this project? Common values are 59 | # 3 - Alpha 60 | # 4 - Beta 61 | # 5 - Production/Stable 62 | 'Development Status :: 3 - Alpha', 63 | 64 | # Indicate who your project is intended for 65 | 'Intended Audience :: Developers', 66 | 'Intended Audience :: End Users/Desktop', 67 | 'Topic :: Games/Entertainment', 68 | 'Topic :: Software Development :: Libraries', 69 | 'Topic :: Software Development :: Libraries :: Python Modules', 70 | 71 | # Pick your license as you wish (should match "license" above) 72 | 'License :: OSI Approved :: MIT License', 73 | 74 | # Specify the Python versions you support here. In particular, ensure 75 | # that you indicate whether you support Python 2, Python 3 or both. 76 | 'Programming Language :: Python :: 3', 77 | 'Programming Language :: Python :: 3.6', 78 | 'Programming Language :: Python :: 3.7', 79 | 'Programming Language :: Python :: 3.8', 80 | 'Programming Language :: Python :: 3 :: Only' 81 | ], 82 | 83 | # What does your project relate to? 84 | keywords='', # TODO 85 | 86 | # You can just specify the packages manually here if your project is 87 | # simple. Or you can use find_packages(). 88 | packages=find_packages(exclude=['contrib', 'docs', 'tests*']), 89 | 90 | # List run-time dependencies here. These will be installed by pip when 91 | # your project is installed. For an analysis of "install_requires" vs pip's 92 | # requirements files see: 93 | # https://packaging.python.org/en/latest/requirements.html 94 | install_requires=['configobj', 'brotli', 'fnvhash', 'cffi'], 95 | 96 | # List additional groups of dependencies here (e.g. development 97 | # dependencies). You can install these using the following syntax, 98 | # for example: 99 | # $ pip install -e .[dev,test] 100 | extras_require=extras_require, 101 | 102 | # If there are data files included in your packages that need to be 103 | # installed, specify them here. If using Python 2.6 or less, then these 104 | # have to be included in MANIFEST.in as well. 105 | package_data={ 106 | 'PyPoE': ['_data/*'], 107 | }, 108 | 109 | # Although 'package_data' is the preferred approach, in some case you may 110 | # need to place data files outside of your packages. See: 111 | # http://docs.python.org/3.8/distutils/setupscript.html#installing-additional-files 112 | # In this case, 'data_file' will be installed into '/my_data' 113 | #data_files=[('my_data', ['data/data_file'])], 114 | data_files=[ 115 | #('', ['PyPoE/_data/*']) 116 | ], 117 | 118 | # To provide executable scripts, use entry points in preference to the 119 | # "scripts" keyword. Entry points provide cross-platform support and allow 120 | # pip to create the appropriate form of executable for the target platform. 121 | entry_points={ # TODO 122 | 'console_scripts': [ 123 | 'pypoe_exporter=PyPoE.cli.exporter.core:main', 124 | ], 125 | 'gui_scripts': [ 126 | 'pypoe_ui=PyPoE.ui:main', 127 | ], 128 | }, 129 | ) 130 | -------------------------------------------------------------------------------- /PyPoE/poe/file/stat_filters.py: -------------------------------------------------------------------------------- 1 | """ 2 | Parser for skillpopup_stat_filters.txt 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/poe/file/stat_filters.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 8c0f2a25e79bc2ed8def2092c3857fd2b946684a $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Parser for Metadata/StatDescriptions/skillpopup_stat_filters.txt 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | 27 | Documentation 28 | =============================================================================== 29 | 30 | Public API 31 | ------------------------------------------------------------------------------- 32 | 33 | .. autoclass:: StatFilterFile 34 | 35 | .. autoclass:: SkillEntry 36 | """ 37 | 38 | # ============================================================================= 39 | # Imports 40 | # ============================================================================= 41 | 42 | # Python 43 | import re 44 | 45 | # 3rd-party 46 | 47 | # self 48 | from PyPoE.shared.mixins import ReprMixin 49 | from PyPoE.poe.file.shared import AbstractFileReadOnly 50 | 51 | # ============================================================================= 52 | # Globals 53 | # ============================================================================= 54 | 55 | __all__ = [] 56 | 57 | # ============================================================================= 58 | # Classes 59 | # ============================================================================= 60 | 61 | 62 | class SkillEntry(ReprMixin): 63 | """ 64 | 65 | Attributes 66 | ---------- 67 | skill_id : str 68 | Id from ActiveSkills.dat 69 | translation_file_path : str 70 | Path to the translation file that should be used for this skill id. 71 | Path is relative to content.ggpk 72 | stats : list[str] 73 | Order in which to display stats 74 | """ 75 | 76 | __slots__ = ['skill_id', 'translation_file_path', 'stats'] 77 | 78 | def __init__(self, skill_id, translation_file_path, stats): 79 | self.skill_id = skill_id 80 | self.translation_file_path = translation_file_path 81 | self.stats = stats 82 | 83 | 84 | class StatFilterFile(AbstractFileReadOnly): 85 | """ 86 | Parser for Metadata/skillpopup_stat_filters.txt 87 | 88 | Attributes 89 | ---------- 90 | groups : dict[str, list[str]] 91 | Dictionary containing stat groups with the id as key, and the list of 92 | stats as value 93 | skills : dict[str, SkillEntry] 94 | Dictionary mapping the active skill id (as key) to a the respective 95 | :class:`SkillEntry` instance as value. 96 | """ 97 | _re_find_sections = re.compile( 98 | # header 99 | r'^(?:' 100 | r'group (?P[\w]+)|' 101 | r'(?P[\w]+) "(?P[\w/\.]+)"' 102 | r')[\r\n]+' 103 | # contents 104 | r'^{' 105 | r'(?P[^}]*)' 106 | r'^}', 107 | re.UNICODE | re.MULTILINE, 108 | ) 109 | 110 | _re_find_contents = re.compile( 111 | r'[\w$]+', 112 | re.UNICODE | re.MULTILINE, 113 | ) 114 | 115 | groups = None 116 | skills = None 117 | 118 | def _read(self, buffer, *args, **kwargs): 119 | data = buffer.read().decode('utf-16') 120 | 121 | self.groups = {} 122 | self.skills = {} 123 | 124 | for match in self._re_find_sections.finditer(data): 125 | contents = self._re_find_contents.findall(match.group('contents')) 126 | if match.group('group'): 127 | self.groups[match.group('group')] = contents 128 | elif match.group('skill_id'): 129 | stats = [] 130 | for stat in contents: 131 | if stat.startswith('$'): 132 | stats.extend(self.groups[stat[1:]]) 133 | else: 134 | stats.append(stat) 135 | self.skills[match.group('skill_id')] = SkillEntry( 136 | skill_id=match.group('skill_id'), 137 | translation_file_path=match.group('file'), 138 | stats=stats, 139 | ) 140 | -------------------------------------------------------------------------------- /PyPoE/cli/exporter/dat/handler.py: -------------------------------------------------------------------------------- 1 | """ 2 | .dat export base handler 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/cli/exporter/dat/handler.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 9de5cd756588fbbaa5f085aa149674d16f8f616f $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | .dat export base handler 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | 34 | # 3rd-party 35 | from tqdm import tqdm 36 | 37 | # self 38 | from PyPoE.poe.constants import VERSION 39 | from PyPoE.poe.file import dat 40 | from PyPoE.cli.core import console, Msg 41 | from PyPoE.cli.exporter import config 42 | from PyPoE.cli.exporter.util import get_content_path 43 | from PyPoE.poe.file.file_system import FileSystem 44 | 45 | # ============================================================================= 46 | # Globals 47 | # ============================================================================= 48 | 49 | __all__ = [] 50 | 51 | # ============================================================================= 52 | # Classes 53 | # ============================================================================= 54 | 55 | 56 | class DatExportHandler: 57 | def add_default_arguments(self, parser): 58 | """ 59 | 60 | :param parser: 61 | :type parser: argparse.ArgumentParser 62 | 63 | :return: 64 | """ 65 | parser.set_defaults(func=self.handle) 66 | parser.add_argument( 67 | '--files', '--file', 68 | help='.dat files to export', 69 | nargs='*', 70 | ) 71 | 72 | parser.add_argument( 73 | '-lang', '--language', 74 | help='Language subdirectory to use', 75 | dest='language', 76 | default=None, 77 | ) 78 | 79 | def handle(self, args): 80 | ver = config.get_option('version') 81 | 82 | if ver != VERSION.DEFAULT: 83 | console('Loading specification for %s' % ver) 84 | dat.set_default_spec(version=ver) 85 | 86 | spec = dat._default_spec 87 | if args.files is None: 88 | args.files = list(spec) 89 | else: 90 | files = set() 91 | 92 | for file_name in args.files: 93 | if file_name in spec: 94 | files.add(file_name) 95 | elif not file_name.endswith('.dat'): 96 | file_name += '.dat' 97 | if file_name not in spec: 98 | console('.dat file "%s" is not in specification. Removing.' % file_name, msg=Msg.error) 99 | else: 100 | files.add(file_name) 101 | 102 | files = list(files) 103 | files.sort() 104 | args.files = files 105 | 106 | args.spec = spec 107 | 108 | def _read_dat_files(self, args, prefix=''): 109 | path = get_content_path() 110 | 111 | console(prefix + 'Loading file system...') 112 | 113 | file_system = FileSystem(root_path=path) 114 | 115 | console(prefix + 'Reading .dat files') 116 | 117 | dat_files = {} 118 | lang = args.language or config.get_option('language') 119 | dir_path = "Data/" 120 | if lang != 'English': 121 | #ggpk_data = index.get_dir_record("Data/%s" % lang) 122 | dir_path = "Data/%s/" % lang 123 | remove = [] 124 | for name in tqdm(args.files): 125 | file_path = dir_path + name 126 | try: 127 | data = file_system.get_file(file_path) 128 | except FileNotFoundError: 129 | console('Skipping "%s" (missing)' % file_path, msg=Msg.warning) 130 | remove.append(name) 131 | continue 132 | 133 | df = dat.DatFile(name) 134 | 135 | df.read(file_path_or_raw=data, use_dat_value=False) 136 | 137 | dat_files[name] = df 138 | 139 | for file_name in remove: 140 | args.files.remove(file_name) 141 | 142 | return dat_files 143 | 144 | 145 | # ============================================================================= 146 | # Functions 147 | # ============================================================================= 148 | -------------------------------------------------------------------------------- /PyPoE/shared/containers.py: -------------------------------------------------------------------------------- 1 | """ 2 | special containers 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | PyPoE/shared/containers.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 391fbd1d266d137544f81ca73bf1ad63443f1bf7 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | special containers 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | """ 27 | 28 | # ============================================================================= 29 | # Imports 30 | # ============================================================================= 31 | 32 | # Python 33 | from collections.abc import Iterable 34 | 35 | # ============================================================================= 36 | # Globals 37 | # ============================================================================= 38 | 39 | __all__ = ['Record', 'TypedContainerMeta', 'TypedContainerMixin', 'TypedList'] 40 | 41 | # ============================================================================= 42 | # Classes 43 | # ============================================================================= 44 | 45 | class Record: 46 | def __str__(self): 47 | return repr(self) 48 | 49 | def __repr__(self): 50 | out = [] 51 | for attr in self.__slots__: 52 | out.append('%s=%s' % (attr, repr(getattr(self, attr)))) 53 | 54 | return '%s(%s)' % (self.__class__.__name__, ', '.join(out)) 55 | 56 | def __eq__(self, other): 57 | if not isinstance(other, self.__class__): 58 | return object.__eq__(self, other) 59 | 60 | for attr in self.__slots__: 61 | if not getattr(self, attr) == getattr(other, attr): 62 | return False 63 | 64 | return True 65 | 66 | def __ne__(self, other): 67 | if not isinstance(other, self.__class__): 68 | return object.__eq__(self, other) 69 | 70 | for attr in self.__slots__: 71 | if not getattr(self, attr) != getattr(other, attr): 72 | return False 73 | 74 | return True 75 | 76 | 77 | class TypedContainerMeta(type): 78 | def __new__(cls, name, bases, attrs): 79 | if 'ACCEPTED_TYPES' not in attrs: 80 | raise ValueError('ACCEPTED_TYPES is required.') 81 | 82 | if attrs['ACCEPTED_TYPES'] is None: 83 | raise ValueError('ACCEPTED_TYPES must not be None.') 84 | 85 | if isinstance(attrs['ACCEPTED_TYPES'], type): 86 | attrs['ACCEPTED_TYPES'] = (attrs['ACCEPTED_TYPES'], ) 87 | elif isinstance(attrs['ACCEPTED_TYPES'], Iterable): 88 | for t in attrs['ACCEPTED_TYPES']: 89 | if not isinstance(t, type): 90 | raise ValueError('Every type in ACCEPTED_TYPES Iterable must be a type') 91 | else: 92 | raise ValueError('ACCEPTED_TYPES must be a type or Iterable of types') 93 | 94 | return type.__new__(cls, name, bases, attrs) 95 | 96 | 97 | class TypedContainerMixin: 98 | 99 | ACCEPTED_TYPES = None 100 | 101 | def _is_cls(self, obj): 102 | if not isinstance(obj, self.__class__): 103 | raise TypeError( 104 | '"%s" instance can only be added to another "%s" instance.' % ( 105 | self.__class__.__name__, 106 | self.__class__.__name__, 107 | ) 108 | ) 109 | 110 | def _is_acceptable(self, obj): 111 | if not isinstance(obj, self.ACCEPTED_TYPES): 112 | raise TypeError('"%s" instance only accepts "%s" instances.' % ( 113 | self.__class__.__name__, 114 | ', '.join([t.__name__ for t in self.ACCEPTED_TYPES]), 115 | )) 116 | 117 | 118 | class TypedList(list, TypedContainerMixin): 119 | def __add__(self, other): 120 | self._is_cls(other) 121 | list.__add__(self, other) 122 | 123 | def __iadd__(self, other): 124 | self._is_cls(other) 125 | list.__iadd__(self, other) 126 | 127 | def __setitem__(self, key, value): 128 | self._is_acceptable(value) 129 | list.__setattr__(self, key, value) 130 | 131 | def append(self, p_object): 132 | self._is_acceptable(p_object) 133 | list.append(self, p_object) 134 | 135 | def extend(self, iterable): 136 | for item in iterable: 137 | self._is_acceptable(item) 138 | list.extend(self, iterable) 139 | 140 | def insert(self, index, p_object): 141 | self._is_acceptable(p_object) 142 | list.insert(self, p_object) -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/test_idl.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for PyPoE.poe.file.idl 3 | 4 | Overview 5 | =============================================================================== 6 | 7 | +----------+------------------------------------------------------------------+ 8 | | Path | tests/PyPoE/poe/file/test_idl.py | 9 | +----------+------------------------------------------------------------------+ 10 | | Version | 1.0.0a0 | 11 | +----------+------------------------------------------------------------------+ 12 | | Revision | $Id: 618060761aa9db7e13c107f3f3d6361e293b0289 $ | 13 | +----------+------------------------------------------------------------------+ 14 | | Author | Omega_K2 | 15 | +----------+------------------------------------------------------------------+ 16 | 17 | Description 18 | =============================================================================== 19 | 20 | Tests for PyPoE/poe/file/idl.py 21 | 22 | Agreement 23 | =============================================================================== 24 | 25 | See PyPoE/LICENSE 26 | 27 | TODO 28 | =============================================================================== 29 | 30 | - IDLFile: 31 | -- __add__ 32 | -- __setitem__ 33 | -- insert 34 | """ 35 | 36 | # ============================================================================= 37 | # Imports 38 | # ============================================================================= 39 | 40 | # Python 41 | import os 42 | from tempfile import TemporaryDirectory 43 | 44 | # 3rd Party 45 | import pytest 46 | 47 | # self 48 | from PyPoE.poe.file.idl import IDLFile, IDLRecord 49 | 50 | # ============================================================================= 51 | # Setup 52 | # ============================================================================= 53 | 54 | cur_dir = os.path.split(os.path.realpath(__file__))[0] 55 | idl_path = os.path.join(cur_dir, '_data', 'test.idl') 56 | idl_path_result = os.path.join(cur_dir, '_data', 'test_write.idl') 57 | 58 | # ============================================================================= 59 | # Fixtures 60 | # ============================================================================= 61 | 62 | data = [ 63 | ["Art/2DArt/Test1", "Art/Textures/Test.dds", 0, 0, 1, 1], 64 | ["Art/2DArt/Test2", "Art/Textures/Test.dds", 1, 0, 2, 1], 65 | ["Art/2DArt/Test3", "Art/Textures/Test.dds", 0, 1, 2, 2], 66 | ] 67 | 68 | 69 | @pytest.fixture 70 | def idl_file(): 71 | return IDLFile() 72 | 73 | @pytest.fixture 74 | def idl_records(): 75 | return [IDLRecord(*row) for row in data] 76 | 77 | @pytest.fixture(params=data) 78 | def idl_record(request): 79 | return IDLRecord(*request.param), request.param 80 | 81 | @pytest.fixture 82 | def idl_record_extra(): 83 | return IDLRecord("Art/2DArt/Extra", "Art/Textures/Test.dds", 0, 2, 3, 3) 84 | 85 | # ============================================================================= 86 | # Tests 87 | # ============================================================================= 88 | 89 | class TestIDLRecord: 90 | def test_init(self, idl_record): 91 | record, d = idl_record 92 | 93 | for i, attr in enumerate(record.__slots__): 94 | assert getattr(record, attr) == d[i], 'Attribute %s mismatch' % attr 95 | 96 | def test_eq(self, idl_record, idl_record_extra): 97 | record, d = idl_record 98 | assert record == record, 'Record should be equal to itself' 99 | assert record == IDLRecord(*d), 'Record should be equal to record initialized with the same data' 100 | assert not record == idl_record_extra, 'Record should not be equal to record initialized with different data' 101 | assert not record == 5, 'Record should not be equal to other data types' 102 | 103 | def test_repr(self, idl_record): 104 | record, d = idl_record 105 | assert eval(repr(record)) == record, 'repr() should return an object that would create a valid IDLRecord' 106 | 107 | class TestIDLFile: 108 | def test_init(self, idl_file): 109 | pass 110 | 111 | def test_append(self, idl_file, idl_records): 112 | # Should work and not raise any errors 113 | for record in idl_records: 114 | idl_file.append(record) 115 | # Should raise type error 116 | with pytest.raises(TypeError): 117 | idl_file.append(5) 118 | idl_file.append('test') 119 | idl_file.append(object()) 120 | 121 | def test_read(self, idl_file, idl_records): 122 | idl_file.read(idl_path) 123 | 124 | for i, record in enumerate(idl_file): 125 | assert record == idl_records[i], 'The records read should match the testing records.' 126 | 127 | def test_write(self, idl_file, idl_records): 128 | for record in idl_records: 129 | idl_file.append(record) 130 | 131 | with TemporaryDirectory() as d: 132 | tmp_path = os.path.join(d, 'test_write.idl') 133 | idl_file.write(tmp_path) 134 | 135 | with open(tmp_path, 'rb') as f1: 136 | with open(idl_path_result, 'rb') as f2: 137 | assert f1.read() == f2.read(), 'Written file should be equal to the write test file.' 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /tests/PyPoE/poe/file/_data/specifications/dat_testspec.py: -------------------------------------------------------------------------------- 1 | """Overview 2 | =============================================================================== 3 | 4 | +----------+------------------------------------------------------------------+ 5 | | Path | tests/PyPoE/poe/file/_data/specifications/ | 6 | +----------+------------------------------------------------------------------+ 7 | | Version | 1.0.0a0 | 8 | +----------+------------------------------------------------------------------+ 9 | | Revision | $Id: 6f41a2dca3434bbc756da356298906fac01f7673 $ | 10 | +----------+------------------------------------------------------------------+ 11 | | Author | Omega_K2 | 12 | +----------+------------------------------------------------------------------+ 13 | 14 | Description 15 | =============================================================================== 16 | 17 | Specification used for tests 18 | 19 | Agreement 20 | =============================================================================== 21 | 22 | See PyPoE/LICENSE 23 | 24 | """ 25 | 26 | # ============================================================================= 27 | # Imports 28 | # ============================================================================= 29 | 30 | # Python 31 | # 3rd-party 32 | 33 | # self 34 | from PyPoE.poe.file.specification.fields import * 35 | 36 | # ============================================================================= 37 | # Globals 38 | # ============================================================================= 39 | 40 | specification = Specification({ 41 | 'TestSpec.dat': File( 42 | fields=( 43 | Field( 44 | name='bool', 45 | type='bool', 46 | ), 47 | Field( 48 | name='byte', 49 | type='byte', 50 | ), 51 | Field( 52 | name='ubyte', 53 | type='ubyte', 54 | ), 55 | Field( 56 | name='short', 57 | type='short', 58 | ), 59 | Field( 60 | name='ushort', 61 | type='ushort', 62 | ), 63 | Field( 64 | name='int', 65 | type='int', 66 | ), 67 | Field( 68 | name='uint', 69 | type='uint', 70 | ), 71 | Field( 72 | name='long', 73 | type='long', 74 | ), 75 | Field( 76 | name='ulong', 77 | type='ulong', 78 | ), 79 | Field( 80 | name='ref|string', 81 | type='ref|string', 82 | ), 83 | Field( 84 | name='ref|list|int', 85 | type='ref|list|int', 86 | ), 87 | Field( 88 | name='ref|ref|ref|int', 89 | type='ref|ref|ref|int', 90 | ), 91 | ), 92 | ), 93 | 'Index.dat': File( 94 | fields=( 95 | Field( 96 | name='int', 97 | type='int', 98 | unique=True, 99 | ), 100 | ), 101 | ), 102 | 'VirtualFields.dat': File( 103 | fields=( 104 | Field( 105 | name='A', 106 | type='int', 107 | ), 108 | Field( 109 | name='B', 110 | type='int', 111 | ), 112 | Field( 113 | name='ListA', 114 | type='ref|list|int', 115 | ), 116 | Field( 117 | name='ListB', 118 | type='ref|list|int', 119 | ), 120 | ), 121 | virtual_fields=( 122 | VirtualField( 123 | name='CombinedA', 124 | fields=('A', 'ListA'), 125 | ), 126 | VirtualField( 127 | name='CombinedB', 128 | fields=('B', 'ListB'), 129 | ), 130 | VirtualField( 131 | name='ZipList', 132 | fields=('ListA', 'ListB'), 133 | zip=True, 134 | ), 135 | VirtualField( 136 | name='CombinedVirtual', 137 | fields=('CombinedA', 'CombinedB'), 138 | ), 139 | ), 140 | ), 141 | 'RelationParent.dat': File( 142 | fields=( 143 | Field( 144 | name='ForeignKey', 145 | type='ulong', 146 | key='RelationChild.dat', 147 | ), 148 | Field( 149 | name='ForeignKeyColumn', 150 | type='int', 151 | key='RelationChild.dat', 152 | key_id='UniqueKey', 153 | ), 154 | ), 155 | ), 156 | 'RelationChild.dat': File( 157 | fields=( 158 | Field( 159 | name='UniqueKey', 160 | type='ulong', 161 | unique=True, 162 | ), 163 | Field( 164 | name='Data', 165 | type='int', 166 | ), 167 | ), 168 | ), 169 | }) 170 | --------------------------------------------------------------------------------