├── .editorconfig ├── .github ├── ISSUE_TEMPLATE.md ├── labels.yml ├── release-drafter.yml ├── renovate.json └── workflows │ ├── constraints.txt │ ├── labeler.yml │ ├── release.yml │ └── tests.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── COPYING ├── HISTORY.rst ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── _static │ └── LiveNovel │ │ ├── backlogbar.gif │ │ ├── calc.html │ │ ├── calccom.html │ │ ├── cgchar.gif │ │ ├── chartlist.html │ │ ├── check.gif │ │ ├── command.html │ │ ├── cond.html │ │ ├── const.html │ │ ├── faq_chart.html │ │ ├── faq_graphic.html │ │ ├── faq_project.html │ │ ├── faq_text.html │ │ ├── faq_var.html │ │ ├── file.html │ │ ├── graphic.html │ │ ├── histbar1.gif │ │ ├── histbar2.gif │ │ ├── histbar3.gif │ │ ├── index.html │ │ ├── inptext.html │ │ ├── interface.html │ │ ├── jump.html │ │ ├── ln_cgmode.png │ │ ├── ln_cond01.png │ │ ├── ln_cond02.png │ │ ├── ln_cond03.png │ │ ├── ln_cond04.png │ │ ├── ln_cond05.png │ │ ├── ln_inptext01.png │ │ ├── ln_inptext02.png │ │ ├── ln_inptext03.png │ │ ├── ln_inptext04.png │ │ ├── ln_inptext05.png │ │ ├── ln_inptext06.png │ │ ├── ln_jump1.png │ │ ├── ln_jump10.png │ │ ├── ln_jump11.png │ │ ├── ln_jump12.png │ │ ├── ln_jump13.png │ │ ├── ln_jump14.png │ │ ├── ln_jump15.png │ │ ├── ln_jump2.png │ │ ├── ln_jump3.png │ │ ├── ln_jump4.png │ │ ├── ln_jump5.png │ │ ├── ln_jump6.png │ │ ├── ln_jump7.png │ │ ├── ln_jump8.png │ │ ├── ln_jump9.png │ │ ├── ln_number.png │ │ ├── ln_open01.gif │ │ ├── ln_open02.gif │ │ ├── ln_open03.gif │ │ ├── ln_open04.gif │ │ ├── ln_open05.gif │ │ ├── ln_open06.gif │ │ ├── ln_open07.gif │ │ ├── ln_open08.gif │ │ ├── ln_open09.gif │ │ ├── ln_open10.gif │ │ ├── ln_open11.gif │ │ ├── ln_open12.gif │ │ ├── ln_open13.gif │ │ ├── ln_open14.gif │ │ ├── ln_open15.gif │ │ ├── ln_open16.gif │ │ ├── ln_open17.gif │ │ ├── ln_open18.gif │ │ ├── ln_open20.gif │ │ ├── ln_open21.gif │ │ ├── ln_open22.gif │ │ ├── ln_open23.gif │ │ ├── ln_open24.gif │ │ ├── ln_open25.gif │ │ ├── ln_selimg01.png │ │ ├── ln_selimg02.png │ │ ├── ln_selimg03.png │ │ ├── ln_selimg04.png │ │ ├── ln_selimg05.png │ │ ├── ln_selimg06.png │ │ ├── ln_selimg07.png │ │ ├── ln_selimg08.png │ │ ├── ln_selimg09.png │ │ ├── ln_selimg10.png │ │ ├── ln_selimg11.png │ │ ├── ln_selimg12.png │ │ ├── ln_selstr01.png │ │ ├── ln_selstr02.png │ │ ├── ln_selstr03.png │ │ ├── ln_selstr04.png │ │ ├── ln_selstr05.png │ │ ├── ln_selstr06.png │ │ ├── ln_selstr07.png │ │ ├── ln_selstr08.png │ │ ├── ln_selstr09.png │ │ ├── ln_selstr10.png │ │ ├── ln_selstr11.png │ │ ├── ln_selstr12.png │ │ ├── ln_turorial01.png │ │ ├── ln_turorial02.png │ │ ├── ln_turorial03.png │ │ ├── ln_turorial04.png │ │ ├── ln_turorial05.png │ │ ├── ln_turorial06.png │ │ ├── ln_turorial07.png │ │ ├── ln_turorial08.png │ │ ├── ln_turorial09.png │ │ ├── ln_turorial10.png │ │ ├── ln_turorial11.png │ │ ├── ln_turorial12.png │ │ ├── ln_turorial13.png │ │ ├── ln_turorial14.png │ │ ├── ln_turorial15.png │ │ ├── ln_turorial16.png │ │ ├── ln_turorial17.png │ │ ├── ln_turorial18.png │ │ ├── ln_turorial19.png │ │ ├── ln_turorial20.png │ │ ├── ln_turorial21.png │ │ ├── ln_turorial22.png │ │ ├── ln_turorial23.png │ │ ├── ln_turorial24.png │ │ ├── ln_turorial25.png │ │ ├── ln_turorial26.png │ │ ├── ln_turorial27.png │ │ ├── ln_turorial28.png │ │ ├── ln_turorial29.png │ │ ├── ln_turorial30.png │ │ ├── ln_turorial31.png │ │ ├── ln_turorial32.png │ │ ├── ln_turorial33.png │ │ ├── ln_turorial34.png │ │ ├── ln_turorial35.png │ │ ├── ln_turorial36.png │ │ ├── ln_turorial37.png │ │ ├── ln_turorial38.png │ │ ├── ln_turorial39.png │ │ ├── ln_turorial40.png │ │ ├── ln_turorial42.png │ │ ├── ln_turorial43.png │ │ ├── ln_turorial44.png │ │ ├── ln_turorial45.png │ │ ├── ln_turorial46.png │ │ ├── ln_turorial47.png │ │ ├── ln_turorial48.png │ │ ├── ln_turorial49.png │ │ ├── ln_turorial50.png │ │ ├── ln_turorial54.png │ │ ├── ln_turorial55.png │ │ ├── ln_turorial56.png │ │ ├── ln_turorial57.png │ │ ├── ln_turorial58.png │ │ ├── ln_turorial59.png │ │ ├── ln_turorial61.png │ │ ├── ln_turorial62.png │ │ ├── ln_turorial63.png │ │ ├── ln_turorial64.png │ │ ├── ln_turorial65.png │ │ ├── ln_turorial66.png │ │ ├── ln_turorial67.png │ │ ├── ln_turorial68.png │ │ ├── ln_turorial69.png │ │ ├── ln_turorial70.png │ │ ├── ln_turorial71.png │ │ ├── ln_turorial72.png │ │ ├── ln_turorial73.png │ │ ├── ln_turorial74.png │ │ ├── ln_turorial75.png │ │ ├── ln_turorial76.png │ │ ├── ln_turorial77.png │ │ ├── ln_turorial78.png │ │ ├── ln_turorial79.png │ │ ├── ln_turorial80.png │ │ ├── ln_turorial81.png │ │ ├── ln_turorial82.png │ │ ├── ln_turorial83.png │ │ ├── ln_turorial84.png │ │ ├── ln_turorial85.png │ │ ├── ln_turorial86.png │ │ ├── ln_turorial87.png │ │ ├── ln_var01.gif │ │ ├── ln_var02.gif │ │ ├── media.html │ │ ├── menu_chart.html │ │ ├── menu_edit.html │ │ ├── menu_file.html │ │ ├── menu_font.html │ │ ├── menu_project.html │ │ ├── menu_scenario.html │ │ ├── menu_var.html │ │ ├── menu_view.html │ │ ├── new.gif │ │ ├── node.html │ │ ├── object.html │ │ ├── openchart.html │ │ ├── openscenario.html │ │ ├── optdlg.gif │ │ ├── optinptext.gif │ │ ├── option.html │ │ ├── option1.html │ │ ├── option10.html │ │ ├── option11.html │ │ ├── option2.html │ │ ├── option3.html │ │ ├── option4.html │ │ ├── option5.html │ │ ├── option6.html │ │ ├── option7.html │ │ ├── option8.html │ │ ├── option9.html │ │ ├── option_frame.html │ │ ├── prop.html │ │ ├── quake1.gif │ │ ├── quake2.gif │ │ ├── quake3.gif │ │ ├── screen.html │ │ ├── script.html │ │ ├── scrollbar.gif │ │ ├── selimg.html │ │ ├── selstr.html │ │ ├── sequence.html │ │ ├── slider_bar.gif │ │ ├── slider_btn.gif │ │ ├── slider_thumb.gif │ │ ├── text.html │ │ ├── topics.html │ │ ├── tutorial01.html │ │ ├── tutorial02.html │ │ ├── tutorial03.html │ │ ├── tutorial04.html │ │ ├── tutorial05.html │ │ ├── tutorial06.html │ │ ├── tutorial07.html │ │ ├── tutorial08.html │ │ ├── tutorial09.html │ │ ├── tutorial10.html │ │ ├── tutorial11.html │ │ ├── tutorial12.html │ │ ├── tutorial13.html │ │ ├── tutorial14.html │ │ ├── tutorial15.html │ │ ├── tutorial16.html │ │ ├── tutorial17.html │ │ ├── tutorial18.html │ │ ├── tutorial19.html │ │ ├── var.html │ │ ├── varlist.html │ │ └── スクリプト例.txt ├── authors.rst ├── cli.rst ├── conf.py ├── contributing.rst ├── history.rst ├── index.rst ├── installation.rst ├── livemaker.cli.rst ├── livemaker.lsb.rst ├── livemaker.rst ├── livenovel.rst ├── livenovel │ ├── basic.rst │ ├── faq.rst │ └── tutorial.rst ├── make.bat ├── modules.rst ├── readme.rst └── usage.rst ├── pyproject.toml ├── requirements_dev.txt ├── setup.cfg ├── src └── livemaker │ ├── .gitignore │ ├── GalImagePlugin.py │ ├── __init__.py │ ├── archive.py │ ├── cli │ ├── __init__.py │ ├── cli.py │ ├── lmar.py │ ├── lmgraph.py │ ├── lmlpb.py │ ├── lmlsb.py │ └── lmpatch.py │ ├── exceptions.py │ ├── lpb.py │ ├── lpm.py │ ├── lsb │ ├── __init__.py │ ├── command.py │ ├── core.py │ ├── graph.py │ ├── lmscript.py │ ├── menu.py │ ├── novel.py │ └── translate.py │ ├── project.py │ └── scramble.py └── tests ├── data ├── 00000001.lsb ├── gamemain.lsb ├── live.lpb ├── save.dat ├── test.dat └── test.exe ├── test_badpadding.py └── test_pylivemaker.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * pylivemaker version: 2 | * Python version: 3 | * Operating System: 4 | * LiveMaker game: 5 | 6 | ### Description 7 | 8 | Describe what you were trying to get done. 9 | Tell us what happened, what went wrong, and what you expected to happen. 10 | 11 | ### What I Did 12 | 13 | ``` 14 | Paste the command(s) you ran and the output. 15 | If there was a crash, please include the traceback here. 16 | ``` 17 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Labels names are important as they are used by Release Drafter to decide 3 | # regarding where to record them in changelog or if to skip them. 4 | # 5 | # The repository labels will be automatically configured using this file and 6 | # the GitHub Action https://github.com/marketplace/actions/github-labeler. 7 | - name: breaking 8 | description: Breaking Changes 9 | color: bfd4f2 10 | - name: bug 11 | description: Something isn't working 12 | color: d73a4a 13 | - name: build 14 | description: Build System and Dependencies 15 | color: bfdadc 16 | - name: ci 17 | description: Continuous Integration 18 | color: 4a97d6 19 | - name: dependencies 20 | description: Pull requests that update a dependency file 21 | color: 0366d6 22 | - name: documentation 23 | description: Improvements or additions to documentation 24 | color: 0075ca 25 | - name: duplicate 26 | description: This issue or pull request already exists 27 | color: cfd3d7 28 | - name: enhancement 29 | description: New feature or request 30 | color: a2eeef 31 | - name: github_actions 32 | description: Pull requests that update Github_actions code 33 | color: "000000" 34 | - name: good first issue 35 | description: Good for newcomers 36 | color: 7057ff 37 | - name: help wanted 38 | description: Extra attention is needed 39 | color: 008672 40 | - name: invalid 41 | description: This doesn't seem right 42 | color: e4e669 43 | - name: performance 44 | description: Performance 45 | color: "016175" 46 | - name: python 47 | description: Pull requests that update Python code 48 | color: 2b67c6 49 | - name: question 50 | description: Further information is requested 51 | color: d876e3 52 | - name: refactoring 53 | description: Refactoring 54 | color: ef67c4 55 | - name: removal 56 | description: Removals and Deprecations 57 | color: 9ae7ea 58 | - name: style 59 | description: Style 60 | color: c120e5 61 | - name: testing 62 | description: Testing 63 | color: b1fc6f 64 | - name: wontfix 65 | description: This will not be worked on 66 | color: ffffff 67 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | categories: 2 | - title: ":boom: Breaking Changes" 3 | label: "breaking" 4 | - title: ":rocket: Features" 5 | label: "enhancement" 6 | - title: ":fire: Removals and Deprecations" 7 | label: "removal" 8 | - title: ":beetle: Fixes" 9 | label: "bug" 10 | - title: ":racehorse: Performance" 11 | label: "performance" 12 | - title: ":rotating_light: Testing" 13 | label: "testing" 14 | - title: ":construction_worker: Continuous Integration" 15 | label: "ci" 16 | - title: ":books: Documentation" 17 | label: "documentation" 18 | - title: ":hammer: Refactoring" 19 | label: "refactoring" 20 | - title: ":lipstick: Style" 21 | label: "style" 22 | - title: ":package: Dependencies" 23 | labels: 24 | - "dependencies" 25 | - "build" 26 | template: | 27 | ## Changes 28 | 29 | $CHANGES 30 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "labels": ["dependencies"], 5 | "packageRules": [ 6 | { 7 | "matchDepTypes": ["dev-dependencies"], 8 | "automerge": true, 9 | "automergeType": "pr" 10 | }, 11 | { 12 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 13 | "automerge": true, 14 | "automergeType": "pr" 15 | } 16 | ], 17 | "automergeStrategy": "rebase", 18 | "platformAutomerge": true, 19 | "rangeStrategy": "auto" 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/constraints.txt: -------------------------------------------------------------------------------- 1 | pip==25.0.1 2 | pytest==8.3.4 3 | pytest-cov==6.0.0 4 | pytest-datadir==1.6.1 5 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: Labeler 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | 9 | jobs: 10 | labeler: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out the repository 14 | uses: actions/checkout@v4 15 | 16 | - name: Run Labeler 17 | uses: crazy-max/ghaction-github-labeler@v5.3.0 18 | with: 19 | skip-delete: true 20 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | release: 8 | types: [released, prereleased] 9 | 10 | jobs: 11 | pypi: 12 | name: PyPI 13 | runs-on: ubuntu-latest 14 | permissions: 15 | id-token: write 16 | steps: 17 | - name: Check out the repository 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Set up Python 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: "3.13" 26 | 27 | - name: Install pip + twine 28 | run: | 29 | pip install -U pip build twine 30 | pip --version 31 | 32 | - name: Install package 33 | run: | 34 | pip install . 35 | 36 | - name: Build package 37 | run: | 38 | python -m build 39 | 40 | - name: Publish package on PyPI 41 | if: ${{ github.event_name == 'release' }} 42 | uses: pypa/gh-action-pypi-publish@v1.12.4 43 | 44 | - name: Publish package on TestPyPI 45 | if: ${{ github.event_name != 'release' }} 46 | uses: pypa/gh-action-pypi-publish@v1.12.4 47 | with: 48 | repository_url: https://test.pypi.org/legacy/ 49 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | tests: 11 | name: ${{ matrix.python }} / ${{ matrix.os }} 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | include: 17 | - os: ubuntu-latest 18 | python: "3.10" 19 | - os: ubuntu-latest 20 | python: "3.11" 21 | - os: ubuntu-latest 22 | python: "3.12" 23 | - os: ubuntu-latest 24 | python: "3.13" 25 | - os: windows-latest 26 | python: "3.13" 27 | - os: macos-latest 28 | python: "3.13" 29 | - os: ubuntu-latest 30 | python: "pypy3.10" 31 | 32 | steps: 33 | - name: Check out the repository 34 | uses: actions/checkout@v4 35 | 36 | - name: Set up Python ${{ matrix.python }} 37 | uses: actions/setup-python@v5 38 | with: 39 | python-version: ${{ matrix.python }} 40 | 41 | - name: Upgrade pip 42 | run: | 43 | pip install --constraint=.github/workflows/constraints.txt pip 44 | pip --version 45 | 46 | - name: Install pylivemaker 47 | run: | 48 | pip install --constraint=.github/workflows/constraints.txt .[test] 49 | 50 | - name: Run tests 51 | run: | 52 | pytest --cov=livemaker --cov-report=xml --cov-report=term 53 | 54 | - name: Upload coverage data 55 | uses: codecov/codecov-action@v5 56 | with: 57 | files: ./coverage.xml 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/psf/black 5 | rev: 25.1.0 6 | hooks: 7 | - id: black 8 | language_version: python3 9 | files: . 10 | - repo: https://github.com/pre-commit/pre-commit-hooks 11 | rev: v5.0.0 12 | hooks: 13 | - id: check-toml 14 | - repo: https://github.com/PyCQA/flake8 15 | rev: 7.1.2 16 | hooks: 17 | - id: flake8 18 | language_version: python3 19 | additional_dependencies: 20 | - flake8-comprehensions 21 | - hooks: 22 | - id: isort 23 | language_version: python3 24 | repo: https://github.com/timothycrosley/isort 25 | rev: 6.0.1 26 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | build: 3 | os: ubuntu-22.04 4 | tools: 5 | python: "3.10" 6 | python: 7 | version: "3.10" 8 | install: 9 | - method: pip 10 | path: . 11 | extra_requirements: 12 | - docs 13 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | pylivemaker dev 6 | --------------- 7 | 8 | * Peter Rowlands 9 | 10 | irl author 11 | ---------- 12 | 13 | * tinfoil 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/pmrowla/pylivemaker/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | pylivemaker could always use more documentation, whether as part of the 42 | official pylivemaker docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/pmrowla/pylivemaker/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `pylivemaker` for local development. 61 | 62 | 1. Fork the `pylivemaker` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/pylivemaker.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv pylivemaker 70 | $ cd pylivemaker/ 71 | $ pip install -e ".[test,docs]" 72 | $ pip install -r requirements_dev.txt 73 | $ pre-commit install 74 | 75 | 4. Create a branch for local development:: 76 | 77 | $ git checkout -b name-of-your-bugfix-or-feature 78 | 79 | Now you can make your changes locally. 80 | 81 | 5. When you're done making changes, check that your changes pass flake8 and the 82 | tests:: 83 | 84 | $ pre-commit run --all-files 85 | $ pytest 86 | 87 | 6. Commit your changes and push your branch to GitHub:: 88 | 89 | $ git add . 90 | $ git commit -m "Your detailed description of your changes." 91 | $ git push origin name-of-your-bugfix-or-feature 92 | 93 | 7. Submit a pull request through the GitHub website. 94 | 95 | Coding Style 96 | ------------ 97 | 98 | Follow PEP8 standards whenever possible, with the following exceptions: 99 | 100 | * Max line length is 119 101 | * PascalCase variable and type names can be used as needed to match LiveMaker's naming conventions. 102 | 103 | Pull Request Guidelines 104 | ----------------------- 105 | 106 | Before you submit a pull request, check that it meets these guidelines: 107 | 108 | 1. The pull request should include tests. 109 | 2. If the pull request adds functionality, the docs should be updated. Put 110 | your new functionality into a function with a docstring, and add the 111 | feature to the list in README.rst. 112 | 3. The pull request should work for Python 3.8 through 3.11 and for PyPy. 113 | 114 | Tips 115 | ---- 116 | 117 | To run a subset of tests:: 118 | 119 | $ pytest tests.test_pylivemaker 120 | 121 | 122 | Deploying 123 | --------- 124 | 125 | A reminder for the maintainers on how to deploy. 126 | Make sure all your changes are committed (including an entry in HISTORY.rst). 127 | Then use the GitHub release functionality to generate a new tag and release. 128 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 1.0.4 (2023-01-01) 6 | ------------------ 7 | 8 | This will be the final release which supports Python <3.8. 9 | 10 | * Fix issue parsing ``TXSPD`` tags 11 | 12 | 1.0.3 (2022-12-26) 13 | ------------------ 14 | 15 | * Fix issue with ``click.get_terminal_size`` deprecation 16 | 17 | 1.0.2 (2021-05-03) 18 | ------------------ 19 | 20 | * Fix issue where text padding could be parsed into None-type 21 | 22 | 1.0.1 (2020-10-25) 23 | ------------------ 24 | 25 | * Fix menu support for certain LM engine versions 26 | * Add experimental ruby/furigana support (supported in LNS scripts only) 27 | 28 | 1.0.0 (2020-07-01) 29 | ------------------ 30 | 31 | * Added ``lmlpb`` tool for editing LPB project parameters 32 | * Added ``livemaker.lsb.translate`` API 33 | * Added menu translation API, text and LPM (image) menus are now supported 34 | * Standardized CSV format for translatable text 35 | * All CSV commands now support the ``--encoding`` parameter 36 | * Fixed old logging bugs 37 | * Added experimental ``lmgraph lsb`` command for generating LSB file execution graphs 38 | * Added ``lmbmp`` helper utility for generating ``BmpToGale`` compatible image + mask pairs 39 | 40 | Known issues: 41 | 42 | * CSV scenario script translation does not currently support formatting tags. 43 | If you need advanced tag support, you will need to use the LNS script 44 | translation method. 45 | 46 | Deprecated: 47 | 48 | * ``--mode=lines`` for scenario text CSV commands 49 | * Old CSV format (CSV files generated in 0.3.x are not compatible with 1.0) 50 | 51 | 0.3.2 (2020-05-04) 52 | ------------------ 53 | 54 | This will be the final release before v1.0.0 (which will break backwards compatibility with this release). 55 | 56 | * Added ``extractcsv`` command for extracting scenario text to a CSV file 57 | * Added ``insertcsv`` command for replacing scenario text from a CSV file 58 | * Added ``lmlpb`` CLI tool for manipulating LPB project settings files. 59 | 60 | Known issues: 61 | 62 | * `extractmenu` and `insertmenu` commands only support using system locale/encoding when reading and writing CSV files. 63 | 64 | Deprecated: 65 | 66 | * Python 3.5 support. 67 | Future releases of pylivemaker will require Python 3.6 and later. 68 | * Existing CSV CLI tool is deprecated. 69 | Future releases of pylivemaker will use a different CSV format which will not be compatible with CSV files generated in this release. 70 | 71 | 0.3.0 (2020-04-30) 72 | ------------------ 73 | 74 | * Added `extractmenu` command for extracting in-game menus to a CSV file 75 | * Added `insertmenu` command for replacing in-game menus from a CSV file 76 | * `lmpatch` now supports batch/recursive patching 77 | 78 | 0.2.1 (2020-03-13) 79 | ------------------ 80 | 81 | * Added `lmgraph` command for generating LSB script call graphs 82 | * Refactored CLI tools (each tool moved to its own source file) 83 | 84 | 0.2.0 (2020-02-16) 85 | ------------------ 86 | 87 | * Added support for reading LM Pro scrambled (encrypted) archives 88 | * ``HAlignEnum`` and ``VAlignEnum`` in ``livemaker/lsb/novel.py`` have been removed and replaced with ``AlignEnum`` 89 | 90 | 0.1.2 (2020-02-05) 91 | ------------------ 92 | 93 | * Added support for split VFF archives 94 | * Added ``lmlsb edit`` command 95 | * Added ``lmlsb batchinsert`` command 96 | * Added support for reading GAL images, and ``galconvert`` CLI tool 97 | 98 | 0.1.0 (2019-03-07) 99 | ------------------ 100 | 101 | * First release on PyPI. 102 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include COPYING 3 | include CONTRIBUTING.rst 4 | include HISTORY.rst 5 | include LICENSE 6 | include README.rst 7 | 8 | recursive-include tests * 9 | recursive-exclude * __pycache__ 10 | recursive-exclude * *.py[co] 11 | 12 | exclude docs/_build 13 | recursive-exclude docs _build * 14 | 15 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif *.html *.txt 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | try: 8 | from urllib import pathname2url 9 | except: 10 | from urllib.request import pathname2url 11 | 12 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 13 | endef 14 | export BROWSER_PYSCRIPT 15 | 16 | define PRINT_HELP_PYSCRIPT 17 | import re, sys 18 | 19 | for line in sys.stdin: 20 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 21 | if match: 22 | target, help = match.groups() 23 | print("%-20s %s" % (target, help)) 24 | endef 25 | export PRINT_HELP_PYSCRIPT 26 | 27 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 28 | 29 | help: 30 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 31 | 32 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 33 | 34 | clean-build: ## remove build artifacts 35 | rm -fr build/ 36 | rm -fr dist/ 37 | rm -fr .eggs/ 38 | find . -name '*.egg-info' -exec rm -fr {} + 39 | find . -name '*.egg' -exec rm -f {} + 40 | 41 | clean-pyc: ## remove Python file artifacts 42 | find . -name '*.pyc' -exec rm -f {} + 43 | find . -name '*.pyo' -exec rm -f {} + 44 | find . -name '*~' -exec rm -f {} + 45 | find . -name '__pycache__' -exec rm -fr {} + 46 | 47 | clean-test: ## remove test and coverage artifacts 48 | rm -fr .tox/ 49 | rm -f .coverage 50 | rm -fr htmlcov/ 51 | rm -fr .pytest_cache 52 | 53 | lint: ## check style with flake8 54 | flake8 livemaker tests 55 | 56 | test: ## run tests quickly with the default Python 57 | py.test 58 | 59 | test-all: ## run tests on every Python version with tox 60 | tox 61 | 62 | coverage: ## check code coverage quickly with the default Python 63 | coverage run --source livemaker -m pytest 64 | coverage report -m 65 | coverage html 66 | $(BROWSER) htmlcov/index.html 67 | 68 | docs: ## generate Sphinx HTML documentation, including API docs 69 | rm -f docs/livemaker.rst 70 | rm -f docs/livemaker.lsb.rst 71 | rm -f docs/modules.rst 72 | sphinx-apidoc -o docs/ livemaker 73 | $(MAKE) -C docs clean 74 | $(MAKE) -C docs html 75 | $(BROWSER) docs/_build/html/index.html 76 | 77 | servedocs: docs ## compile the docs watching for changes 78 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 79 | 80 | release: dist ## package and upload a release 81 | twine upload dist/* 82 | 83 | dist: clean ## builds source and wheel package 84 | python setup.py sdist 85 | python setup.py bdist_wheel 86 | ls -l dist 87 | 88 | install: clean ## install the package to the active Python's site-packages 89 | python setup.py install 90 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | pylivemaker 3 | =========== 4 | 5 | 6 | .. image:: https://img.shields.io/pypi/v/pylivemaker.svg 7 | :target: https://pypi.python.org/pypi/pylivemaker 8 | 9 | .. image:: https://github.com/pmrowla/pylivemaker/actions/workflows/tests.yml/badge.svg?branch=master 10 | :target: https://github.com/pmrowla/pylivemaker/actions/workflows/tests.yml 11 | 12 | .. image:: https://readthedocs.org/projects/pylivemaker/badge/?version=latest 13 | :target: https://pylivemaker.readthedocs.io/en/latest/?badge=latest 14 | :alt: Documentation Status 15 | 16 | 17 | 18 | 19 | Python package for manipulating LiveMaker 3 game resources. 20 | Specifically intended to work with LiveNovel VN's, but extraction 21 | should also work for other LiveMaker games. 22 | 23 | Based on tinfoil's irl_. 24 | 25 | Requires Python 3 (3.10 and later). 26 | 27 | 28 | * Free software: GNU General Public License v3 29 | * Documentation: https://pylivemaker.readthedocs.io. 30 | 31 | .. _irl: https://bitbucket.org/tinfoil/irl 32 | 33 | 34 | Features 35 | -------- 36 | 37 | * Extract files from a LiveMaker .exe or .dat file. 38 | * Dump LSB files to human-readable text or XML (similar to LiveMaker's XML .lsc format). 39 | * Extract LiveNovel LNS scripts from LSB files. 40 | * Compile (modified) LNS scripts and insert them into LSB files. 41 | * Patch (modified) LSB files into an existing .exe or .dat file. 42 | 43 | License 44 | ------- 45 | 46 | pylivemaker / irl 47 | ^^^^^^^^^^^^^^^^^ 48 | 49 | Copyright (C) 2020 Peter Rowlands 50 | 51 | Copyright (C) 2014 tinfoil 52 | 53 | This program is free software: you can redistribute it and/or modify 54 | it under the terms of the GNU General Public License as published by 55 | the Free Software Foundation, either version 3 of the License, or 56 | (at your option) any later version. 57 | 58 | This program is distributed in the hope that it will be useful, 59 | but WITHOUT ANY WARRANTY; without even the implied warranty of 60 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 61 | GNU General Public License for more details. 62 | 63 | You should have received a copy of the GNU General Public License 64 | along with this program. If not, see . 65 | 66 | Python 67 | ^^^^^^ 68 | 69 | Copyright (c) 2001-2019 Python Software Foundation. All rights reserved. 70 | 71 | Credits 72 | ------- 73 | 74 | This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. 75 | 76 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 77 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 78 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = pylivemaker 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/LiveNovel/backlogbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/backlogbar.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/calc.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/calc.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/calccom.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/calccom.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/cgchar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/cgchar.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/chartlist.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/chartlist.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/check.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/check.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/command.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/command.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/cond.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/cond.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/const.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/const.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/faq_chart.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/faq_chart.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/faq_graphic.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/faq_graphic.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/faq_project.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/faq_project.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/faq_text.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/faq_text.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/faq_var.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/faq_var.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/file.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/file.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/graphic.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/graphic.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/histbar1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/histbar1.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/histbar2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/histbar2.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/histbar3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/histbar3.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | LiveNovel 9 | 10 | 11 | 12 | 13 | 14 | <BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#800080" ALINK="#800080"> 15 | <BASEFONT SIZE=3> 16 | <P></P> 17 | </BODY> 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/_static/LiveNovel/inptext.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/inptext.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/interface.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/interface.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/jump.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/jump.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_cgmode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_cgmode.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_cond01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_cond01.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_cond02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_cond02.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_cond03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_cond03.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_cond04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_cond04.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_cond05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_cond05.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_inptext01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_inptext01.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_inptext02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_inptext02.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_inptext03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_inptext03.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_inptext04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_inptext04.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_inptext05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_inptext05.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_inptext06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_inptext06.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump1.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump10.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump11.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump12.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump13.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump14.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump15.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump2.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump3.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump4.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump5.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump6.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump7.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump8.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_jump9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_jump9.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_number.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open01.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open02.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open03.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open04.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open04.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open05.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open05.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open06.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open06.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open07.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open07.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open08.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open08.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open09.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open09.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open10.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open11.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open12.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open13.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open13.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open14.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open15.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open15.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open16.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open17.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open17.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open18.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open18.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open20.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open21.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open21.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open22.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open22.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open23.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open23.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open24.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_open25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_open25.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selimg01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selimg01.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selimg02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selimg02.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selimg03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selimg03.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selimg04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selimg04.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selimg05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selimg05.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selimg06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selimg06.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selimg07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selimg07.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selimg08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selimg08.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selimg09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selimg09.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selimg10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selimg10.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selimg11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selimg11.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selimg12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selimg12.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selstr01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selstr01.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selstr02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selstr02.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selstr03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selstr03.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selstr04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selstr04.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selstr05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selstr05.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selstr06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selstr06.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selstr07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selstr07.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selstr08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selstr08.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selstr09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selstr09.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selstr10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selstr10.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selstr11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selstr11.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_selstr12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_selstr12.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial01.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial02.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial03.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial04.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial05.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial06.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial07.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial08.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial09.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial10.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial11.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial12.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial13.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial14.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial15.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial16.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial17.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial18.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial19.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial20.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial21.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial22.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial23.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial24.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial25.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial26.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial27.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial28.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial29.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial30.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial31.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial32.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial33.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial34.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial35.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial36.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial37.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial38.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial39.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial40.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial42.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial43.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial44.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial45.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial46.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial47.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial48.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial49.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial49.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial50.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial54.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial55.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial56.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial57.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial58.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial59.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial59.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial61.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial61.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial62.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial62.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial63.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial63.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial64.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial65.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial65.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial66.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial66.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial67.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial67.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial68.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial68.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial69.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial69.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial70.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial71.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial71.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial72.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial73.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial73.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial74.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial74.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial75.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial76.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial77.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial77.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial78.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial78.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial79.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial79.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial80.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial81.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial81.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial82.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial82.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial83.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial83.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial84.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial84.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial85.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial85.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial86.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial86.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_turorial87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_turorial87.png -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_var01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_var01.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/ln_var02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/ln_var02.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/media.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/media.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/menu_chart.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/menu_chart.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/menu_edit.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/menu_edit.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/menu_file.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/menu_file.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/menu_font.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/menu_font.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/menu_project.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/menu_project.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/menu_scenario.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/menu_scenario.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/menu_var.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/menu_var.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/menu_view.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/menu_view.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/new.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/new.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/node.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/node.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/object.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/object.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/openchart.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/openchart.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/openscenario.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/openscenario.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/optdlg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/optdlg.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/optinptext.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/optinptext.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/option.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/option.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/option1.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/option1.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/option10.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/option10.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/option11.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/option11.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/option2.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/option2.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/option3.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/option3.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/option4.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/option4.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/option5.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/option5.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/option6.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/option6.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/option7.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/option7.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/option8.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/option8.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/option9.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/option9.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/option_frame.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/option_frame.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/prop.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/prop.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/quake1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/quake1.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/quake2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/quake2.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/quake3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/quake3.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/screen.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/screen.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/script.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/script.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/scrollbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/scrollbar.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/selimg.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/selimg.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/selstr.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/selstr.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/sequence.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/sequence.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/slider_bar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/slider_bar.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/slider_btn.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/slider_btn.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/slider_thumb.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/slider_thumb.gif -------------------------------------------------------------------------------- /docs/_static/LiveNovel/text.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/text.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/topics.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/topics.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial01.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial01.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial02.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial02.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial03.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial03.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial04.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial04.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial05.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial05.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial06.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial06.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial07.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial07.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial08.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial08.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial09.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial09.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial10.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial10.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial11.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial11.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial12.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial12.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial13.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial13.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial14.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial14.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial15.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial15.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial16.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial16.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial17.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial17.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial18.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial18.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/tutorial19.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/tutorial19.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/var.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/var.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/varlist.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/varlist.html -------------------------------------------------------------------------------- /docs/_static/LiveNovel/スクリプト例.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmrowla/pylivemaker/ceeb12911c571fe6da77107bb0eaf7eb29594612/docs/_static/LiveNovel/スクリプト例.txt -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # pylivemaker documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another 16 | # directory, add these directories to sys.path here. If the directory is 17 | # relative to the documentation root, use os.path.abspath to make it 18 | # absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | 23 | import livemaker 24 | 25 | sys.path.insert(0, os.path.abspath("..")) 26 | 27 | # -- General configuration --------------------------------------------- 28 | 29 | # If your documentation needs a minimal Sphinx version, state it here. 30 | # 31 | # needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 35 | extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.napoleon"] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ["_templates"] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # 43 | # source_suffix = ['.rst', '.md'] 44 | source_suffix = ".rst" 45 | 46 | # The master toctree document. 47 | master_doc = "index" 48 | 49 | # General information about the project. 50 | project = "pylivemaker" 51 | copyright = "2019, Peter Rowlands" 52 | author = "Peter Rowlands" 53 | 54 | # The version info for the project you're documenting, acts as replacement 55 | # for |version| and |release|, also used in various other places throughout 56 | # the built documents. 57 | # 58 | # The short X.Y version. 59 | version = livemaker.__version__ 60 | # The full version, including alpha/beta/rc tags. 61 | release = livemaker.__version__ 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | language = None 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | # This patterns also effect to html_static_path and html_extra_path 73 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 74 | 75 | # The name of the Pygments (syntax highlighting) style to use. 76 | pygments_style = "sphinx" 77 | 78 | # If true, `todo` and `todoList` produce output, else they produce nothing. 79 | todo_include_todos = False 80 | 81 | 82 | # -- Options for HTML output ------------------------------------------- 83 | 84 | # The theme to use for HTML and HTML Help pages. See the documentation for 85 | # a list of builtin themes. 86 | # 87 | html_theme = "alabaster" 88 | 89 | # Theme options are theme-specific and customize the look and feel of a 90 | # theme further. For a list of options available for each theme, see the 91 | # documentation. 92 | # 93 | # html_theme_options = {} 94 | 95 | # Add any paths that contain custom static files (such as style sheets) here, 96 | # relative to this directory. They are copied after the builtin static files, 97 | # so a file named "default.css" will overwrite the builtin "default.css". 98 | html_static_path = ["_static"] 99 | 100 | 101 | # -- Options for HTMLHelp output --------------------------------------- 102 | 103 | # Output file base name for HTML help builder. 104 | htmlhelp_basename = "pylivemakerdoc" 105 | 106 | 107 | # -- Options for LaTeX output ------------------------------------------ 108 | 109 | latex_elements = { 110 | # The paper size ('letterpaper' or 'a4paper'). 111 | # 112 | # 'papersize': 'letterpaper', 113 | # The font size ('10pt', '11pt' or '12pt'). 114 | # 115 | # 'pointsize': '10pt', 116 | # Additional stuff for the LaTeX preamble. 117 | # 118 | # 'preamble': '', 119 | # Latex figure (float) alignment 120 | # 121 | # 'figure_align': 'htbp', 122 | } 123 | 124 | # Grouping the document tree into LaTeX files. List of tuples 125 | # (source start file, target name, title, author, documentclass 126 | # [howto, manual, or own class]). 127 | latex_documents = [ 128 | (master_doc, "pylivemaker.tex", "pylivemake Documentation", "Peter Rowlands", "manual"), 129 | ] 130 | 131 | 132 | # -- Options for manual page output ------------------------------------ 133 | 134 | # One entry per manual page. List of tuples 135 | # (source start file, name, description, authors, manual section). 136 | man_pages = [(master_doc, "pylivemaker", "pylivemaker Documentation", [author], 1)] 137 | 138 | 139 | # -- Options for Texinfo output ---------------------------------------- 140 | 141 | # Grouping the document tree into Texinfo files. List of tuples 142 | # (source start file, target name, title, author, 143 | # dir menu entry, description, category) 144 | texinfo_documents = [ 145 | ( 146 | master_doc, 147 | "pylivemaker", 148 | "pylivemaker Documentation", 149 | author, 150 | "pylivemaker", 151 | "One line description of project.", 152 | "Miscellaneous", 153 | ), 154 | ] 155 | 156 | # -- autodoc options 157 | 158 | autodoc_member_order = "bysource" 159 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to pylivemaker's documentation! 2 | ======================================= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | readme 9 | installation 10 | usage 11 | cli 12 | modules 13 | contributing 14 | authors 15 | history 16 | 17 | livenovel 18 | 19 | Original LM3 LiveNovel Docs (jp) 20 | 21 | Indices and tables 22 | ================== 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | 8 | Stable release 9 | -------------- 10 | 11 | To install pylivemaker, run this command in your terminal: 12 | 13 | .. code-block:: console 14 | 15 | $ pip install pylivemaker 16 | 17 | This is the preferred method to install pylivemaker, as it will always install the most recent stable release. 18 | 19 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide 20 | you through the process. 21 | 22 | .. _pip: https://pip.pypa.io 23 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ 24 | 25 | 26 | From sources 27 | ------------ 28 | 29 | The sources for pylivemaker can be downloaded from the `Github repo`_. 30 | 31 | You can either clone the public repository: 32 | 33 | .. code-block:: console 34 | 35 | $ git clone git://github.com/pmrowla/pylivemaker 36 | 37 | Or download the `tarball`_: 38 | 39 | .. code-block:: console 40 | 41 | $ curl -OL https://github.com/pmrowla/pylivemaker/tarball/master 42 | 43 | Once you have a copy of the source, you can install it with: 44 | 45 | .. code-block:: console 46 | 47 | $ python setup.py install 48 | 49 | 50 | .. _Github repo: https://github.com/pmrowla/pylivemaker 51 | .. _tarball: https://github.com/pmrowla/pylivemaker/tarball/master 52 | -------------------------------------------------------------------------------- /docs/livemaker.cli.rst: -------------------------------------------------------------------------------- 1 | livemaker.cli package 2 | ===================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | livemaker.cli.cli module 8 | ------------------------ 9 | 10 | .. automodule:: livemaker.cli.cli 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | livemaker.cli.lmar module 16 | ------------------------- 17 | 18 | .. automodule:: livemaker.cli.lmar 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | livemaker.cli.lmgraph module 24 | ---------------------------- 25 | 26 | .. automodule:: livemaker.cli.lmgraph 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | livemaker.cli.lmlpb module 32 | -------------------------- 33 | 34 | .. automodule:: livemaker.cli.lmlpb 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | livemaker.cli.lmlsb module 40 | -------------------------- 41 | 42 | .. automodule:: livemaker.cli.lmlsb 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | livemaker.cli.lmpatch module 48 | ---------------------------- 49 | 50 | .. automodule:: livemaker.cli.lmpatch 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | 56 | Module contents 57 | --------------- 58 | 59 | .. automodule:: livemaker.lsb 60 | :members: 61 | :undoc-members: 62 | :show-inheritance: 63 | -------------------------------------------------------------------------------- /docs/livemaker.lsb.rst: -------------------------------------------------------------------------------- 1 | livemaker.lsb package 2 | ===================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | livemaker.lsb.command module 8 | ---------------------------- 9 | 10 | .. automodule:: livemaker.lsb.command 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | livemaker.lsb.core module 16 | ------------------------- 17 | 18 | .. automodule:: livemaker.lsb.core 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | livemaker.lsb.graph module 24 | -------------------------- 25 | 26 | .. automodule:: livemaker.lsb.graph 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | livemaker.lsb.lmscript module 32 | ----------------------------- 33 | 34 | .. automodule:: livemaker.lsb.lmscript 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | livemaker.lsb.menu module 40 | ------------------------- 41 | 42 | .. automodule:: livemaker.lsb.menu 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | livemaker.lsb.novel module 48 | -------------------------- 49 | 50 | .. automodule:: livemaker.lsb.novel 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | livemaker.lsb.translate module 56 | ------------------------------ 57 | 58 | .. automodule:: livemaker.lsb.translate 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | 64 | Module contents 65 | --------------- 66 | 67 | .. automodule:: livemaker.lsb 68 | :members: 69 | :undoc-members: 70 | :show-inheritance: 71 | -------------------------------------------------------------------------------- /docs/livemaker.rst: -------------------------------------------------------------------------------- 1 | livemaker package 2 | ================= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | livemaker.lsb 10 | livemaker.cli 11 | 12 | Submodules 13 | ---------- 14 | 15 | livemaker.archive module 16 | ------------------------ 17 | 18 | .. automodule:: livemaker.archive 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | livemaker.exceptions module 24 | --------------------------- 25 | 26 | .. automodule:: livemaker.exceptions 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | livemaker.lpb module 32 | -------------------- 33 | 34 | .. automodule:: livemaker.lpb 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | livemaker.lpm module 40 | -------------------- 41 | 42 | .. automodule:: livemaker.lpm 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | livemaker.project module 48 | ------------------------ 49 | 50 | .. automodule:: livemaker.project 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | livemaker.scramble module 56 | ------------------------- 57 | 58 | .. automodule:: livemaker.scramble 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | 64 | Module contents 65 | --------------- 66 | 67 | .. automodule:: livemaker 68 | :members: 69 | :undoc-members: 70 | :show-inheritance: 71 | -------------------------------------------------------------------------------- /docs/livenovel.rst: -------------------------------------------------------------------------------- 1 | LiveNovel Docs (en) 2 | =================== 3 | 4 | .. note:: This is a rough/WIP English translation of the LiveMaker 3 LiveNovel help documents. 5 | It also contains some notes and annotations regarding LiveMaker's binary LSB format and pylivemaker behavior. 6 | 7 | This document should not be treated as a formal reference manual for LiveMaker/LiveNovel. 8 | When in doubt, refer to the original Japanese help documents. 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | :caption: Contents 13 | 14 | livenovel/tutorial 15 | livenovel/basic 16 | livenovel/menu 17 | livenovel/compute 18 | livenovel/faq 19 | -------------------------------------------------------------------------------- /docs/livenovel/basic.rst: -------------------------------------------------------------------------------- 1 | LiveNovel Basic Information 2 | =========================== 3 | 4 | Screen layout 5 | ------------- 6 | 7 | How to use the chart window 8 | --------------------------- 9 | 10 | How to use the scenario window 11 | ------------------------------ 12 | 13 | Chart list 14 | ---------- 15 | 16 | Charts can be organized into folders. 17 | You can change folder order and chart order with the up and down arrow buttons on the left. 18 | Right-click to bring up a pop-up menu. 19 | 20 | .. image:: /_static/LiveNovel/ln_turorial64.png 21 | :alt: Chart list window 22 | 23 | フォルダ作成 (Create folder) 24 | Create a new subfolder inside the selected folder. 25 | 26 | 追加 (Add) 27 | Create a new chart. 28 | 29 | 複製 (Duplicate) 30 | Duplicate the selected chart. 31 | 32 | 開く (Open) 33 | Open the selected chart. 34 | 35 | 新規ウィンドウで開く (Open in new window) 36 | Open the selected chart selected in a new window. 37 | 38 | 削除 (Delete) 39 | Delete the selected chart. 40 | 41 | プロパティ (Properties) 42 | Display the properties for the selected chart. 43 | 44 | Variable list 45 | ------------- 46 | 47 | .. image:: /_static/LiveNovel/ln_var02.gif 48 | :alt: Variable list window 49 | 50 | Variables set in ``変数リスト`` (Variable list) are global. 51 | They can be accessed (both read/write) from all charts and nodes (as opposed to chart and compute node variables, which have limited scope). 52 | 53 | .. list-table:: 54 | :header-rows: 1 55 | 56 | * - Item 57 | - Description 58 | * - 名前 (Name) 59 | - Must be unique (cannot overlap with a chart or calculation variable). 60 | * - タイプ (Type) 61 | - 整数 (Integer) 62 | Signed integer number. 63 | 64 | 実数 (Real) 65 | Signed decimal number. 66 | 67 | フラグ (Flag) 68 | Boolean flag, either ``TRUE`` or ``FALSE``. 69 | 70 | 文字列 (String) 71 | Variable length (CP932 encoded) string. 72 | * - 初期値 (Default value) 73 | - Initial value stored in the variable. 74 | * - 動作タイプ (Operation type) 75 | - 通常 (Normal) 76 | Initialized at game start-up and reset at the following: 77 | 78 | - Player returns to title screen via system menu. 79 | - Game enters Scene Recollection mode 80 | - Title screen is displayed after a game over 81 | 82 | 起動時のみ初期化 (Start-up only initialization) 83 | Only initialized at game start-up. 84 | 85 | ステータス (Status) 86 | Only initialized when game is started for the very first time after installation. 87 | Value is saved when the game is exited, and restored when the game is relaunched. 88 | 89 | Node type 90 | --------- 91 | 92 | Properties 93 | ---------- 94 | 95 | Scenario script 96 | --------------- 97 | 98 | HTML-like scenario script file syntax. 99 | 100 | * Attributes with a default value are optional. 101 | All other attributes are required. 102 | * Tags are not case-sensitive. 103 | * Lines starting with a semicolon (``;``) are treated as comments. 104 | If you need to begin an actual line with a semicolon, escape it with ``\;``. 105 | * When using special characters (``{``, ``}``, ``<``, ``>``, ``\``) escape them with a preceding backslash (``\``). 106 | 107 | Sample script: :ref:`First scenario from "GTE·SP"` 108 | 109 | .. note:: pylivemaker attempts to approximate this script syntax when extracting scenario scripts, but the syntax used by pylivemaker is not a one-to-one match with LiveMaker's syntax. 110 | 111 | A majority of these tags are compiled into one or more LM system events when generating a binary LSB, and as a result, the original tags cannot be recovered/decompiled by pylivemaker. 112 | 113 | .. list-table:: 114 | :header-rows: 1 115 | 116 | * - Tag 117 | - Description 118 | * -
119 | - 120 | * - 121 | - 122 | * - 123 | - 124 | * - 125 | - 126 | * - 127 | - 128 | * - 129 | - 130 | * - 131 | - 132 | * - 133 | - 134 | * - 135 | - 136 | * - 137 | - 138 | * - 139 | - 140 | * - 141 | - 142 | * - 143 | - 144 | * - 145 | - 146 | * - 147 | - 148 | * - 149 | - 150 | * - 151 | - 152 | * - 153 | - 154 | * - 155 | - 156 | * - 157 | - 158 | * - 159 | - 160 | * - 161 | - 162 | * - 163 | - 164 | * - 165 | - 166 | * - 167 | - 168 | * - 169 | - 170 | * - 171 | - 172 | * - 173 | - 174 | * - 175 | - 176 | * - 177 | - 178 | * - 179 | - 180 | * - 181 | - 182 | * - 183 | - 184 | * - 185 | - 186 | * - 187 | - 188 | * - 189 | - 190 | * - 191 | - 192 | * - 193 | - 194 | * - 195 | - 196 | * - 197 | - 198 | * - 199 | - 200 | * - 201 | - 202 | * - 203 | - 204 | * - 205 | - 206 | * - 207 | - 208 | * - 209 | - 210 | * - 211 | - 212 | * - 213 | - 214 | * - 215 | - 216 | * - 217 | - 218 | * - 219 | - 220 | 221 | Chart and calculation variables 222 | ------------------------------- 223 | 224 | Jump 225 | ---- 226 | 227 | Conditionals 228 | ------------ 229 | 230 | Text string choice selection 231 | ---------------------------- 232 | 233 | Image choice selection 234 | ---------------------- 235 | 236 | Character string input 237 | ---------------------- 238 | 239 | Project options 240 | --------------- 241 | 242 | Variable / value 243 | ---------------- 244 | 245 | System variables 246 | ---------------- 247 | 248 | Several special system variables exist. 249 | System variables begin with ``@`` and can be used to get or set system status. 250 | 251 | .. list-table:: Standard system variables 252 | :header-rows: 1 253 | 254 | * - Variable name 255 | - Type 256 | - Description 257 | - Read/Write 258 | * - ``@Year`` 259 | - Integer 260 | - Year for current date. 261 | - Read-only 262 | * - ``@Month`` 263 | - Integer 264 | - Month for current date. 265 | - Read-only 266 | * - ``@Day`` 267 | - Integer 268 | - Day for current date. 269 | - Read-only 270 | * - ``@Week`` 271 | - Integer 272 | - Weekday from ``1`` (Sun) to ``7`` (Sat) for current date. 273 | - Read-only 274 | * - ``@Hour`` 275 | - Integer 276 | - Hour for current time. 277 | - Read-only 278 | * - ``@Min`` 279 | - Integer 280 | - Minute for current time. 281 | - Read-only 282 | * - ``@Sec`` 283 | - Integer 284 | - Second for current time. 285 | - Read-only 286 | * - ``@1Hour`` 287 | - Real 288 | - 289 | - Read-only 290 | * - ``@1Min`` 291 | - Real 292 | - 293 | - Read-only 294 | * - ``@1Sec`` 295 | - Real 296 | - 297 | - Read-only 298 | * - ``@MouseX`` 299 | - Integer 300 | - Current mouse cursor X-coordinate. 301 | - Read/Write 302 | * - ``@MouseY`` 303 | - Integer 304 | - Current mouse cursor Y-coordinate. 305 | - Read/Write 306 | * - ``@LClick`` 307 | - Flag 308 | - ``TRUE`` when left mouse button is clicked. 309 | - Read-only 310 | * - ``@RClick`` 311 | - Flag 312 | - ``TRUE`` when right mouse button is clicked. 313 | - Read-only 314 | * - ``@LPush`` 315 | - Flag 316 | - ``TRUE`` if left mouse button is pressed. 317 | - Read-only 318 | * - ``@RPush`` 319 | - Flag 320 | - ``TRUE`` if right mouse button is pressed. 321 | - Read-only 322 | * - ``@KeyClick[KEY_CONST]`` 323 | - Flag 324 | - ``TRUE`` when the specified key is clicked. 325 | - Read-only 326 | * - ``@KeyPush[KEY_CONST]`` 327 | - Flag 328 | - ``TRUE`` if the specified key is pressed. 329 | - Read-only 330 | * - ``@KeyRepeat[KEY_CONST]`` 331 | - Flag 332 | - ``TRUE`` if the specified key is being pressed at regular intervals. 333 | - Read-only 334 | * - ``@Pi`` 335 | - Real 336 | - Value of Pi. 337 | - Read-only 338 | * - ``@TickCount`` 339 | - Integer 340 | - Time since the program was started in milliseconds. 341 | - Read-only 342 | * - ``@PCTickCount`` 343 | - Integer 344 | - Time since Windows was started in milliseconds. 345 | - Read-only 346 | * - ``@Title`` 347 | - String 348 | - Software title. 349 | - Read-only 350 | * - ``@FullScr`` 351 | - Flag 352 | - ``TRUE`` if program is in full-screen mode. 353 | - Read/Write 354 | * - ``@ScrHeight`` 355 | - Integer 356 | - Screen height in pixels 357 | - Read-only 358 | * - ``@ScrWidth`` 359 | - Integer 360 | - Screen width in pixels 361 | - Read-only 362 | * - ``@SelIndex`` 363 | - Integer 364 | - 0-indexed value of the player's last text or image selection choice. 365 | For image selection, it will contain the same value displayed in ``LivePreviewMenu``. 366 | 367 | * Value will be set to ``-1`` if selection prompt was closed by right-clicking. 368 | - Read/Write 369 | * - ``@SelStr`` 370 | - Integer 371 | - Text string value of the player's last text or image selection choice. 372 | For image selection, it will contain the name given in ``LivePreviewMenu``. 373 | - Read/Write 374 | 375 | .. list-table:: LiveMaker Pro system variables 376 | :header-rows: 1 377 | 378 | * - Variable name 379 | - Type 380 | - Description 381 | - Read/Write 382 | * - ``@ActiveCompo`` 383 | - String 384 | - Name of the active component for receiving keyboard input. 385 | - Read/Write 386 | * - ``@CGDither`` 387 | - Flag 388 | - If TRUE, perform full color image dithering. 389 | - Read/Write 390 | * - ``@Cursor`` 391 | - String 392 | - Default mouse cursor filename. 393 | If a component cursor is set, it takes precedence over this one. 394 | - Read/Write 395 | * - ``@DisplayModeCount`` 396 | - Integer 397 | - Number of available full-screen display modes. 398 | - Read-only 399 | * - ``@DisplayModeIndex`` 400 | - Integer 401 | - Index of the current full-screen display mode. 402 | Setting this value will immediately enable full-screen mode. 403 | - Read/Write 404 | * - ``@FixedFonts`` 405 | - String 406 | - List of fixed-width TrueType fonts available on this system, separated by newlines. 407 | - Read-only 408 | * - ``@Fonts`` 409 | - String 410 | - List of TrueType fonts available on this system, separated by newlines. 411 | - Read-only 412 | * - ``@HistoryCount`` 413 | - Integer 414 | - When history format is registered 415 | Text history height (in pixels) 416 | 417 | When history format is not registered 418 | Number of available text histories 419 | - Read-only 420 | * - ``@HistoryMaxCount`` 421 | - Integer 422 | - Maximum number of text history pages that can be stored. 423 | 424 | When history format is registered 425 | Maximum number of history message box pages 426 | 427 | When history format is not registered 428 | Number of pages for text insertion 429 | - Read/Write 430 | * - ``@IsDebug`` 431 | - Flag 432 | - ``TRUE`` when running in debug mode. 433 | - Read-only 434 | * - ``@ParamStr`` 435 | - String 436 | - Parameter argument array. 437 | Available range is ``@ParamStr[0] ~ @ParamStr[@ParamCount - 1]``. 438 | - Read-only 439 | * - ``@Result`` 440 | - - 441 | - Value to be returned to the caller. 442 | - Read/Write 443 | * - ``@Sender`` 444 | - String 445 | - Name of the invoked component. 446 | - Read-only 447 | * - ``@SoundON`` 448 | - Flag 449 | - Setting to ``FALSE`` will disable audio playback. 450 | - Read/Write 451 | * - ``@StackCount`` 452 | - Integer 453 | - Number of call stacks 454 | - Read-only 455 | * - ``@Temp`` 456 | - - 457 | - Can be used as a substitute when assigning variables with ``AssignTemp``. 458 | - Read/Write 459 | * - ``@TextDelay`` 460 | - Integer 461 | - Default weight for inserted text. 462 | - ``Read/Write`` 463 | * - ``@MidiPort`` 464 | - String 465 | - Current MIDI port. 466 | - Read/Write 467 | * - ``@MidiPorts[Index]`` 468 | - String 469 | - Available MIDI ports. 470 | Index can be ``0 ~ (GetArraySize(@MidiPorts) - 1)`` 471 | - Read-only 472 | 473 | Operator 474 | -------- 475 | 476 | Function 477 | -------- 478 | -------------------------------------------------------------------------------- /docs/livenovel/faq.rst: -------------------------------------------------------------------------------- 1 | LiveNovel FAQ 2 | ============= 3 | 4 | Project 5 | ------- 6 | 7 | Cannot select folder for "New Project" 8 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 9 | 10 | Please specify an empty folder. 11 | A directory cannot be used if it contains any files or subdirectories. 12 | 13 | Cannot exit game at runtime 14 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 15 | 16 | If the system menu is disabled, you will not be able to exit the game via menus. 17 | In this case, use ALT-F4 to exit the game. 18 | 19 | Specifying the text string displayed for a save file 20 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 21 | 22 | The text string displayed for a save file is normally the in-game text content displayed at the time of the save. 23 | If you assign a text string to the variable ``セーブキャプション`` (``SaveCaption``), the string will be displayed (with date and time appended). 24 | 25 | Example (Using compute nodes):: 26 | 27 | セーブキャプション = "In front of school - 3:00PM" 28 | 29 | This state will be retained until the next time that variable is changed. 30 | If the variable is cleared, the default behavior will be restored. :: 31 | 32 | セーブキャプション = "" 33 | 34 | Chart 35 | ----- 36 | 37 | Chart is too narrow 38 | ~~~~~~~~~~~~~~~~~~~ 39 | 40 | Right click on the chart and select ``チャートのプロパティ`` (``Chart Properties``) from the menu. 41 | When the properties are displayed, change ``チャートサイズ`` (``Chart Size``) and click ``OK``. 42 | 43 | Text 44 | ---- 45 | 46 | Changing message box design 47 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 48 | 49 | Select ``プロジェクト`` → ``オプション`` (``Project`` → ``Options``) from the menu to open the Options dialog, and set ``メッセージボックス`` → ``ボックス`` → ``デザイン`` (``Message Box`` → ``Box`` → ``Design``). 50 | If ``タイプ`` (``Type``) is set to ``画像`` (``Image``), the specified image file can be used as a message box. 51 | 52 | Changing message box click sound 53 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 54 | 55 | Select ``プロジェクト`` → ``オプション`` from the menu to open the Options dialog and set ``メッセージボックス`` → ``ボックス`` → ``サウンド`` → ``改ページ`` (``Message Box`` → ``Box`` → ``Sound`` → ``Page Break``) 56 | 57 | Removing a line from scenario recollection mode 58 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 59 | 60 | Text which has already been displayed can be re-read from the Scenario Recollection option in the system menu, but there may be text which you want to be hidden in this mode. 61 | In that case, right-click the scenario node and check ``説明文として扱う`` (``Treat as description``) (the node color will change to green). 62 | 63 | Graphic 64 | ------- 65 | 66 | Displaying two images at the same time 67 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 68 | 69 | Insert two ``画像表示`` (``Display Image``) commands and set ``「画像表示」の「画面効果」`` (``Display Image`` → ``Screen Effect``) for the first image to ``裏画面に配置`` (``Background placement``). 70 | This way, when the second display command is executed, the first image will also be displayed. 71 | 72 | Displaying and hiding images at the same time 73 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 74 | 75 | Insert a ``画像表示`` (``Display Image``) command and ``画像消去`` (``Delete Image``) command in that order, and set ``「画像表示」の「画面効果」`` (``Display Image`` → ``Screen Effect``) to ``裏画面に配置`` (``Background placement``). 76 | This way, when the delete command is executed, the first image will be displayed. 77 | 78 | Image is not displayed 79 | ~~~~~~~~~~~~~~~~~~~~~~ 80 | 81 | If ``「画像表示」の「プライオリティ」`` (``Display Image`` → ``Priority``) is low, it may be hidden behind other images. 82 | 83 | Sound 84 | ----- 85 | 86 | Calculation/Variable 87 | -------------------- 88 | 89 | Installer 90 | --------- 91 | -------------------------------------------------------------------------------- /docs/livenovel/tutorial.rst: -------------------------------------------------------------------------------- 1 | LiveNovel Tutorial 2 | ================== 3 | 4 | Source file preparation 5 | ~~~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | Project preparation 8 | ~~~~~~~~~~~~~~~~~~~ 9 | 10 | Scenario creation preparation 11 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 12 | 13 | Display background 14 | ~~~~~~~~~~~~~~~~~~ 15 | 16 | Display text 17 | ~~~~~~~~~~~~ 18 | 19 | Display character 20 | ~~~~~~~~~~~~~~~~~ 21 | 22 | Change character 23 | ~~~~~~~~~~~~~~~~ 24 | 25 | Remove characters 26 | ~~~~~~~~~~~~~~~~~ 27 | 28 | Display name 29 | ~~~~~~~~~~~~ 30 | 31 | Display face 32 | ~~~~~~~~~~~~ 33 | 34 | Branch by choice 35 | ~~~~~~~~~~~~~~~~ 36 | 37 | Branch by image selection 38 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 39 | 40 | Branch by flag 41 | ~~~~~~~~~~~~~~ 42 | 43 | Play sound 44 | ~~~~~~~~~~ 45 | 46 | Use multiple charts 47 | ~~~~~~~~~~~~~~~~~~~ 48 | 49 | Add CG mode 50 | ~~~~~~~~~~~ 51 | 52 | Add Scenario Recollection mode 53 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 54 | 55 | Branch in second playthrough 56 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 57 | 58 | Master creation 59 | ~~~~~~~~~~~~~~~ 60 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=pylivemaker 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | livemaker 8 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | API Examples 5 | ------------ 6 | 7 | Look at the `livemaker.cli` and `livemaker.patch` modules for usage examples. 8 | 9 | Patching Example 10 | ---------------- 11 | 12 | To try and patch something: 13 | 14 | 1. Extract game files. :: 15 | 16 | $ mkdir game_files 17 | $ lmar x game.exe -o game_files 18 | 19 | 2. Dump some lsb files. 20 | I suggest starting with ``ゲームメイン.lsb`` (``gamemain.lsb``), and then looking for the first user script to be called (generally ``00000001.lsb``) and continue from there. 21 | If you are familiar with LiveNovel, ``ゲームメイン.lsb`` is the root game start chart node. :: 22 | 23 | $ cd game_files 24 | $ lmlsb dump ゲームメイン.lsb > gamemain.txt 25 | $ lmlsb dump 00000001.lsb > 00000001.lsb.txt 26 | 27 | 3. Once you've found an LSB with a script you want to edit, scenario text lines from an LSB can be extracted into a CSV file. :: 28 | 29 | $ lmlsb extractcsv --encoding=utf-8-sig 00000001.lsb 00000001.csv 30 | 31 | .. note:: It is also possible to extract, translate and insert multiple LSB files 32 | using a single CSV file. For details refer to this discussion: 33 | https://github.com/pmrowla/pylivemaker/issues/58#issuecomment-626787758 34 | 35 | 4. Edit the CSV using your preferred spreadsheet tool. To translate a text block, 36 | simply add your translation in the "Translated text" column. 37 | 38 | .. image:: https://user-images.githubusercontent.com/651988/80898950-d770ae00-8d44-11ea-8772-99e49f3b8482.png 39 | :width: 500 40 | 41 | .. note:: For text blocks spanning multiple lines, newlines in the translated 42 | text cell will be preserved in-game as line breaks. 43 | 44 | 5. Patch the translated CSV back into the lsb. :: 45 | 46 | $ lmlsb insertcsv --encoding=utf-8-sig 00000001.lsb 00000001.csv 47 | 48 | .. note:: The recommended way to translate scripts in pylivemaker 1.0 49 | is via the CSV tools. ``extractmenu`` and ``insertmenu`` can be used 50 | to translate in-game text menus via CSV files as well. For more details 51 | on the CSV tools, refer to this (and related) github discussions: 52 | https://github.com/pmrowla/pylivemaker/pull/37 53 | 54 | 6. Patch the exe (for now the .lsb file must be in the same directory as the exe, since there is no command line option to set the correct archive entry path). :: 55 | 56 | $ lmpatch some.exe 00000001.lsb 57 | 58 | Patching with LNS scripts 59 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 60 | 61 | LNS scripts can be extracted and edited as well, in the event that you 62 | need support for more advanced script tags which are not supported by 63 | the CSV translation API. 64 | 65 | Example (replaces steps 3 through 5 from above): 66 | 67 | 3. Once you've found an lsb with a script you want to edit, extract it. :: 68 | 69 | $ mkdir orig_scripts 70 | $ lmlsb extract 00000001.lsb -o orig_scripts 71 | 72 | 4. Edit the script. :: 73 | 74 | $ mkdir translated_scripts 75 | $ cp orig_scripts/*.lns translated_scripts 76 | 77 | 78 | 5. Patch the new script back into the lsb. :: 79 | 80 | $ lmlsb insert 00000001.lsb scripts_dir/.lns 1234 81 | 82 | (where 1234 is the appropriate TextIns command line number). 83 | 84 | Notes for Translation Patches 85 | ----------------------------- 86 | 87 | Latin Character Display 88 | ^^^^^^^^^^^^^^^^^^^^^^^ 89 | 90 | By default, LiveMaker games will display text using full-width latin characters, which causes text translated into any Western language to look very bad in-game. 91 | For LiveMaker 3 based games, this behavior can be modified, but for LiveMaker 2 games, I am unaware of any solution for this issue. 92 | 93 | To force LiveMaker 3 games to display text using half-width latin characters, the ``PR_FONTCHANGEABLED`` parameter must be set to ``0`` for the given message box type. 94 | This can be handled by using the ``lmlsb edit`` pylivemaker command. 95 | 96 | The default settings for each LiveMaker message box type are set via ``MesNew`` commands, in the system ``メッセージボックス作成.lsb`` (create_message_box.lsb) file. 97 | For the standard in-game text, users will want to modify the command corresponding to the ``メッセージボックス`` (message_box) box type (box type is the first parameter to ``MesNew``). 98 | In most cases, this should be command number 36 in ``メッセージボックス作成.lsb``. 99 | 100 | Example:: 101 | 102 | $ lmlsb edit メッセージボックス作成.lsb 36 103 | 36: MesNew "メッセージボックス" "メッセージボックス土台" 10 10 GetProp("メッセージボックス土台", 5) - 10 - 10 GetProp("メッセージボックス土台", 6) - 10 - 10 104 | 1100 "MS ゴシック" 16 6 16777215 16711680 0 16776960 1 0 "ノベルシステム\メッセージボックス\再生中.lsc" "ノベルシステム\メッセージボックス\イベント.lsc" 105 | "ノベルシステム\メッセージボックス\右クリック時.lsc" "ノベルシステム\メッセージボックス\終了.lsc" "ノベルシステム\メッセージボックス\リンク.lsc" 1 4 0 106 | "ノベルシステム\メッセージボックス\再生開始.lsc" "ノベルシステム\メッセージボックス\アイドル時.lsc" 0 0 0 0 1 0 107 | 108 | Enter new value for each field (or keep existing value) 109 | Name ["メッセージボックス"]: 110 | PR_PARENT ["メッセージボックス土台"]: 111 | PR_LEFT [10]: 112 | PR_TOP [10]: 113 | PR_WIDTH [GetProp("メッセージボックス土台", 5) - 10 - 10]: 114 | PR_HEIGHT [GetProp("メッセージボックス土台", 6) - 10 - 10]: 115 | PR_ALPHA []: 116 | PR_PRIORITY [1100]: 117 | ... 118 | PR_TAG []: 119 | PR_CAPTURELINK [1]: 120 | PR_FONTCHANGEABLED [1]: 0 121 | PR_PADDINGLEFT []: 122 | PR_PADDING_RIGHT []: 123 | Backing up original LSB. 124 | Wrote new LSB. 125 | 126 | In the above example, ``lmlsb edit`` is used to modify command #36 within ``メッセージボックス作成.lsb``. 127 | The existing values (shown in ``[]`` brackets) are kept for every field except for ``PR_FONTCHANGEABLED``. 128 | By changing that value to ``0``, the standard in-game text box should now be displayed using half-width latin characters. 129 | 130 | For more details refer to the thread in `issue #9 `_. 131 | 132 | .. note:: There are multiple possible LiveMaker message box types (including menus/history/etc), so users generating a full translation patch may need to modify multiple ``MesBox`` commands to have their translated text displayed properly everywhere in-game. 133 | 134 | Translating System Menus 135 | ^^^^^^^^^^^^^^^^^^^^^^^^ 136 | 137 | To translate the main LiveMaker system menus, ``lmlsb edit`` can be used to translate strings in the system menu LSB files (such as ``ノベルシステム/システムメニュー/オプション選択時.lsb``/``novel_system/system_menu/on_option_selection.lsb``). 138 | 139 | For more details refer to the thread in `issue #19 `_. 140 | 141 | pylivemaker-tools 142 | ^^^^^^^^^^^^^^^^^ 143 | 144 | Github user @Stefan311 has provided a collection of scripts which may be useful to translators. 145 | See: https://github.com/Stefan311/pylivemaker-tools. 146 | 147 | In particular, ``extractstrings.py`` and ``insertstrings.py`` can be used to translate strings inside LSB commands which are not supported by pylivemaker's ``lmlsb edit`` tool. 148 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=45", "setuptools-scm[toml]>=7"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "pylivemaker" 7 | description = "Python package for manipulating LiveMaker game resources." 8 | readme = "README.rst" 9 | requires-python = ">=3.10" 10 | license = {text = "GNU General Public License v3 or later (GPLv3+)"} 11 | authors = [ 12 | {name = "Peter Rowlands", email = "peter@pmrowla.com"}, 13 | {name = "tinfoil"}, 14 | ] 15 | keywords = [ 16 | "LiveMaker", 17 | ] 18 | classifiers = [ 19 | "Development Status :: 5 - Production/Stable", 20 | "Intended Audience :: Developers", 21 | "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", 22 | "Natural Language :: English", 23 | "Programming Language :: Python :: 3", 24 | "Programming Language :: Python :: 3.10", 25 | "Programming Language :: Python :: 3.11", 26 | "Programming Language :: Python :: 3.12", 27 | "Programming Language :: Python :: 3.13", 28 | ] 29 | dependencies = [ 30 | "Click>=8.1", 31 | "construct>=2.10,<2.11", 32 | "funcy>=1.4", 33 | "loguru>=0.4.1", 34 | "lxml>=4.3", 35 | "networkx>=2.4", 36 | "numpy>=1.16", 37 | "Pillow>=10.2.0", 38 | "pydot>=1.4.1", 39 | ] 40 | dynamic = ["version"] 41 | 42 | [project.optional-dependencies] 43 | test = [ 44 | "coverage>=7", 45 | "pytest>=5", 46 | "pytest-datadir>=1.3.1", 47 | "pytest-cov>=4.0.0", 48 | ] 49 | docs = [ 50 | "Sphinx>=6", 51 | ] 52 | 53 | [project.urls] 54 | documentation = "https://pylivemaker.readthedocs.io/en/latest/" 55 | repository = "https://github.com/pmrowla/pylivemaker" 56 | changelog = "https://github.com/pmrowla/pylivemaker/blob/master/HISTORY.rst" 57 | 58 | [project.scripts] 59 | lmar = "livemaker.cli:lmar" 60 | lmgraph = "livemaker.cli:lmgraph" 61 | lmlpb = "livemaker.cli:lmlpb" 62 | lmlsb = "livemaker.cli:lmlsb" 63 | lmpatch = "livemaker.cli:lmpatch" 64 | galconvert = "livemaker.cli:galconvert" 65 | lmbmp = "livemaker.cli:lmbmp" 66 | 67 | [tool.black] 68 | line-length = 119 69 | include = '\.pyi?$' 70 | exclude = ''' 71 | /( 72 | \.git 73 | | \.eggs 74 | | \.mypy_cache 75 | | \.tox 76 | | \.venv 77 | | \.github 78 | | _build 79 | | build 80 | | dist 81 | )/ 82 | ''' 83 | 84 | [tool.isort] 85 | line_length = 119 86 | 87 | [tool.setuptools.packages.find] 88 | where = ["src"] 89 | 90 | [tool.setuptools_scm] 91 | write_to = "src/livemaker/_version.py" 92 | local_scheme = "no-local-version" 93 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pip>=22.3.1 2 | flake8>=6.0.0 3 | pre-commit>=2.21.0 4 | coverage>=7.0.1 5 | Sphinx>=6.0.0 6 | twine>=4.0.2 7 | 8 | pytest>=7.2.0 9 | pytest-cov>=4.0.0 10 | pytest-datadir>=1.4.1 11 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = .git,docs 3 | ignore = E203, E226, E503, E743, W503 4 | max-line-length = 119 5 | -------------------------------------------------------------------------------- /src/livemaker/.gitignore: -------------------------------------------------------------------------------- 1 | _version.py 2 | -------------------------------------------------------------------------------- /src/livemaker/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2019 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | """Top-level package for pylivemaker.""" 19 | from loguru import logger 20 | 21 | from ._version import __version__, __version_tuple__ # noqa: F401 22 | from .archive import LMArchive, LMArchiveInfo, LMCompressType 23 | from .lsb import LMScript, LNSCompiler, LNSDecompiler 24 | 25 | __all__ = ["LMArchive", "LMArchiveInfo", "LMCompressType", "LMScript", "LNSCompiler", "LNSDecompiler"] 26 | 27 | 28 | logger.disable("livemaker") 29 | -------------------------------------------------------------------------------- /src/livemaker/cli/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # -*- coding: utf-8 -*- 19 | """pylivemaker cli.""" 20 | 21 | import os 22 | import sys 23 | 24 | from loguru import logger 25 | 26 | from .cli import galconvert, lmbmp 27 | from .lmar import lmar 28 | from .lmgraph import lmgraph 29 | from .lmlpb import lmlpb 30 | from .lmlsb import lmlsb 31 | from .lmpatch import lmpatch 32 | 33 | __all__ = ["galconvert", "lmar", "lmbmp", "lmgraph", "lmlpb", "lmlsb", "lmpatch"] 34 | 35 | 36 | logger.remove() 37 | 38 | if os.name == "nt": 39 | sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace") 40 | 41 | logger.add(sys.stderr, level="WARNING") 42 | logger.enable("livemaker") 43 | -------------------------------------------------------------------------------- /src/livemaker/cli/cli.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2019 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # -*- coding: utf-8 -*- 19 | """pylivemaker cli.""" 20 | 21 | import sys 22 | from pathlib import Path 23 | 24 | import click 25 | from PIL import Image 26 | 27 | from livemaker import GalImagePlugin, __version__ # noqa: F401 28 | 29 | _version = """%(prog)s, version %(version)s 30 | 31 | Copyright (C) 2019 Peter Rowlands 32 | Copyright (C) 2014 tinfoil 33 | 34 | This program is free software: you can redistribute it and/or modify it under 35 | the terms of the GNU General Public License as published by the Free Software 36 | Foundation, either version 3 of the License, or (at your option) any later 37 | version. 38 | 39 | This program is distributed in the hope that it will be useful, but WITHOUT 40 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 41 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 42 | 43 | You should have received a copy of the GNU General Public License along with 44 | this program. If not, see . 45 | """ 46 | 47 | 48 | @click.command() 49 | @click.version_option(version=__version__, message=_version) 50 | @click.option("-f", "--force", is_flag=True, default=False, help="Overwrite output file if it exists.") 51 | @click.argument("input_file", required=True, type=click.Path(exists=True, dir_okay=False)) 52 | @click.argument("output_file", required=True, type=click.Path(dir_okay=False)) 53 | def galconvert(force, input_file, output_file): 54 | """Convert the image to another format. 55 | 56 | GAL(X) images can only be read (for conversion to JPEG/PNG/etc) at this time. 57 | 58 | Output format will be determined based on file extension. 59 | 60 | """ 61 | try: 62 | im = Image.open(input_file) 63 | except OSError as e: 64 | sys.exit(f"Error opening {input_file}: {e}") 65 | if Path(output_file).exists() and not force: 66 | sys.exit(f"{output_file} already exists") 67 | print(f"Converting {input_file} to {output_file}") 68 | im.load() 69 | im.save(output_file) 70 | 71 | 72 | @click.command() 73 | @click.version_option(version=__version__, message=_version) 74 | @click.option("-f", "--force", is_flag=True, default=False, help="Overwrite output file if it exists.") 75 | @click.argument("input_file", required=True, type=click.Path(exists=True, dir_okay=False)) 76 | def lmbmp(force, input_file): 77 | """Convert image to BMP(s) which can be used with bmp2gale. 78 | 79 | If the input file contains an alpha layer, a mask bitmap will be generated. 80 | Output files will be named .bmp and -m.bmp. 81 | """ 82 | input_file = Path(input_file) 83 | try: 84 | im = Image.open(input_file) 85 | except OSError as e: 86 | sys.exit(f"Error opening {input_file}: {e}") 87 | name = Path(input_file.stem) 88 | output_file = input_file.parent / f"{name}.bmp" 89 | if Path(output_file).exists() and not force: 90 | sys.exit(f"{output_file} already exists") 91 | 92 | try: 93 | mask = im.getchannel("A") 94 | output_mask = input_file.parent / f"{name}-m.bmp" 95 | if Path(output_mask).exists() and not force: 96 | sys.exit(f"{output_mask} already exists") 97 | print(f"Generating mask {output_mask}") 98 | mask.save(output_mask) 99 | except ValueError: 100 | pass 101 | 102 | print(f"Converting {input_file} to {output_file}") 103 | im = im.convert("RGB") 104 | im.save(output_file) 105 | -------------------------------------------------------------------------------- /src/livemaker/cli/lmar.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # -*- coding: utf-8 -*- 19 | """LiveMaker archive CLI tool.""" 20 | 21 | from io import BytesIO 22 | from pathlib import Path 23 | 24 | import click 25 | from PIL import Image 26 | 27 | from livemaker import GalImagePlugin # noqa: F401 28 | from livemaker.archive import LMArchive 29 | from livemaker.exceptions import BadLiveMakerArchive, LiveMakerException 30 | 31 | from .cli import __version__, _version 32 | 33 | 34 | @click.group() 35 | @click.version_option(version=__version__, message=_version) 36 | def lmar(): 37 | """Command-line tool for manipulating LiveMaker archives and EXEs.""" 38 | pass 39 | 40 | 41 | def _extract_as_png(lm, info, output_dir, image_format, dry_run, verbose): 42 | try: 43 | png_path = info.path.parent.joinpath(f"{info.path.stem}.png") 44 | if not dry_run: 45 | data = lm.read(info) 46 | path = output_dir.joinpath(png_path).expanduser().resolve() 47 | im = Image.open(BytesIO(data)) 48 | path.parent.mkdir(parents=True, exist_ok=True) 49 | im.save(path) 50 | if verbose or dry_run: 51 | print(png_path) 52 | except LiveMakerException as e: 53 | print(f"Error converting {info.path} to PNG: {e}") 54 | if image_format == "png": 55 | print(" Original GAL image will be used as fallback.") 56 | if not dry_run: 57 | lm.extract(info, output_dir) 58 | if verbose or dry_run: 59 | print(info.path) 60 | 61 | 62 | @lmar.command() 63 | @click.option( 64 | "-n", "--dry-run", is_flag=True, default=False, help="Show what would be done without extracting any files." 65 | ) 66 | @click.option( 67 | "-i", 68 | "--image-format", 69 | type=click.Choice(["gal", "png", "both"]), 70 | default="gal", 71 | help="Format for extracted images, defaults to GAL (original) format. If set to png, images will be" 72 | " converted before extraction. If set to both, both the original GAL and converted PNG images will" 73 | " be extracted", 74 | ) 75 | @click.option("-o", "--output-dir", nargs=1, help="Output directory, defaults to current working directory.") 76 | @click.option("-v", "--verbose", is_flag=True, default=False) 77 | @click.argument("input_file", metavar="file", required=True, type=click.Path(exists=True, dir_okay=False)) 78 | def x(dry_run, image_format, output_dir, verbose, input_file): 79 | """Extract the specified archive.""" 80 | if output_dir: 81 | output_dir = Path(output_dir) 82 | else: 83 | output_dir = Path.cwd() 84 | try: 85 | with LMArchive(input_file) as lm: 86 | for info in lm.infolist(): 87 | try: 88 | if info.path.suffix.lower() == ".gal": 89 | if image_format in ("gal", "both"): 90 | if not dry_run: 91 | lm.extract(info, output_dir) 92 | if verbose or dry_run: 93 | print(info.path) 94 | if image_format in ("png", "both"): 95 | _extract_as_png(lm, info, output_dir, image_format, dry_run, verbose) 96 | else: 97 | if not dry_run: 98 | lm.extract(info, output_dir) 99 | if verbose or dry_run: 100 | print(info.path) 101 | except LiveMakerException as e: 102 | print(f" Error extracting {info.path}: {e}") 103 | except BadLiveMakerArchive as e: 104 | print(f"Could not read LiveMaker archive {input_file}: {e}") 105 | 106 | 107 | @lmar.command() 108 | @click.argument("input_file", required=True, type=click.Path(exists=True, dir_okay=False)) 109 | def l(input_file): # noqa: E741 110 | """List the contents of the specified archive.""" 111 | try: 112 | with LMArchive(input_file) as lm: 113 | lm.list() 114 | except BadLiveMakerArchive as e: 115 | print(f"Could not read LiveMaker archive {input_file}: {e}") 116 | 117 | 118 | @lmar.command() 119 | @click.argument("input_file", required=True, type=click.Path(exists=True, dir_okay=False)) 120 | @click.argument("output_file", required=True, type=click.Path(dir_okay=False, writable=True)) 121 | def strip(input_file, output_file): 122 | """Copy the specified LiveMaker EXE but remove the LiveMaker archive. 123 | 124 | The resulting program cannot be run, but may be useful for reverse engineering or patching reasons. 125 | 126 | """ 127 | try: 128 | with LMArchive(input_file) as lm: 129 | if lm.is_exe: 130 | path = Path(output_file) 131 | if path.exists(): 132 | print(f"{path} already exists and will be overwritten.") 133 | with open(output_file, "wb") as f: 134 | f.write(lm.read_exe()) 135 | else: 136 | print("The specified file is not a LiveMaker executable.") 137 | except BadLiveMakerArchive as e: 138 | print(f"Could not read LiveMaker archive {input_file}: {e}") 139 | -------------------------------------------------------------------------------- /src/livemaker/cli/lmgraph.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # -*- coding: utf-8 -*- 19 | """pylivemaker lsb call graph tool.""" 20 | 21 | import sys 22 | from collections import deque 23 | from pathlib import Path 24 | 25 | import click 26 | import pydot 27 | 28 | from livemaker.exceptions import LiveMakerException 29 | from livemaker.lsb import LMScript 30 | from livemaker.lsb.command import CommandType 31 | from livemaker.lsb.graph import make_graph, nx_to_dot 32 | 33 | from .cli import __version__, _version 34 | 35 | 36 | @click.group() 37 | @click.version_option(version=__version__, message=_version) 38 | def lmgraph(): 39 | """Experimental command-line tool for generating DOT syntax graphs.""" 40 | 41 | 42 | IGNORED_SCRIPTS = [ 43 | "メッセージボックス作成.lsb", 44 | "メッセージボックス座標.lsb", 45 | ] 46 | 47 | 48 | visited = set() 49 | lsbs_to_visit = deque() 50 | graph = pydot.Dot(graph_type="digraph") 51 | 52 | 53 | def parse_lsb(lsb_file, root_dir=None): 54 | """Parse one LSB into the graph.""" 55 | if root_dir: 56 | path = root_dir.joinpath(lsb_file) 57 | else: 58 | path = lsb_file 59 | if path in visited: 60 | return 61 | visited.add(path) 62 | print(f"processing {path}...") 63 | with open(path, "rb") as f: 64 | try: 65 | lsb = LMScript.from_file(f) 66 | except LiveMakerException as e: 67 | sys.exit(f"Could not open LSB file: {e}") 68 | graph.add_node(pydot.Node(str(lsb_file), label=str(lsb_file))) 69 | remaining_cmds = set(range(1, len(lsb.commands))) 70 | 71 | # very naive attempt at determining condition for jumping to a new script 72 | cmds_to_visit = deque([(0, None)]) 73 | while cmds_to_visit: 74 | pc, last_calc = cmds_to_visit.popleft() 75 | cmd = lsb.commands[pc] 76 | if cmd.type == CommandType.Jump: 77 | ref = cmd.get("Page") 78 | calc = str(cmd.get("Calc")) 79 | 80 | if ref.Page == lsb_file: 81 | if calc != "1": 82 | # branch not taken 83 | next_pc = pc + 1 84 | if next_pc in remaining_cmds: 85 | remaining_cmds.remove(next_pc) 86 | cmds_to_visit.append((next_pc, last_calc)) 87 | if calc != "0": 88 | # branch taken 89 | if calc == "1": 90 | calc = last_calc 91 | next_pc = ref.Label 92 | if next_pc in remaining_cmds: 93 | remaining_cmds.remove(next_pc) 94 | cmds_to_visit.append((next_pc, calc)) 95 | elif not ref.Page.startswith("ノベルシステム"): 96 | if last_calc: 97 | edge = pydot.Edge(lsb_file, ref.Page, label=last_calc) 98 | else: 99 | edge = pydot.Edge(lsb_file, ref.Page) 100 | graph.add_edge(edge) 101 | lsbs_to_visit.append(ref.Page) 102 | elif cmd.type == CommandType.Call: 103 | ref = cmd.get("Page") 104 | calc = str(cmd.get("Calc")) 105 | 106 | if ref.Page != lsb_file and not ref.Page.startswith("ノベルシステム") and ref.Page not in IGNORED_SCRIPTS: 107 | # ignore calls to self (used for cleanup sometimes) and 108 | # novel system calls 109 | if last_calc: 110 | edge = pydot.Edge(lsb_file, ref.Page, label=last_calc) 111 | else: 112 | edge = pydot.Edge(lsb_file, ref.Page) 113 | graph.add_edge(edge) 114 | lsbs_to_visit.append(ref.Page) 115 | 116 | next_pc = pc + 1 117 | if next_pc in remaining_cmds: 118 | remaining_cmds.remove(next_pc) 119 | cmds_to_visit.append((next_pc, last_calc)) 120 | elif cmd.type not in (CommandType.Exit, CommandType.Terminate, CommandType.PCReset): 121 | next_pc = pc + 1 122 | if next_pc in remaining_cmds: 123 | remaining_cmds.remove(next_pc) 124 | cmds_to_visit.append((next_pc, last_calc)) 125 | 126 | 127 | @lmgraph.command() 128 | @click.argument("lsb_file", required=True, type=click.Path(exists=True, dir_okay=False)) 129 | @click.argument("out_file", required=False) 130 | def game(lsb_file, out_file): 131 | """Generate a DOT syntax call graph for a full LiveNovel game. 132 | 133 | lsb_file should be a path to the root script node - this should always be ゲームメイン.lsb (game_main.lsb) 134 | for LiveMaker games. 135 | If output file is not specified, it defaults to .dot 136 | 137 | The output graph will start with game_main as the root node and follow branches to all scenario 138 | scripts, which should give a general approximation of the original LiveMaker scenario chart. 139 | """ 140 | path = Path(lsb_file) 141 | print(f"Generating graph for {path}") 142 | if path.name != "ゲームメイン.lsb": 143 | print("Warning: input filename is not ゲームメイン.lsb") 144 | root_dir = path.parent 145 | lsbs_to_visit.append(path.name) 146 | while lsbs_to_visit: 147 | parse_lsb(lsbs_to_visit.popleft(), root_dir=root_dir) 148 | if not out_file: 149 | out_file = f"{lsb_file}.dot" 150 | with open(out_file, "w") as f: 151 | f.write(graph.to_string()) 152 | print(f"Wrote {out_file}") 153 | 154 | 155 | @lmgraph.command() 156 | @click.argument("lsb_file", required=True, type=click.Path(exists=True, dir_okay=False)) 157 | @click.argument("out_file", required=False) 158 | def lsb(lsb_file, out_file): 159 | """Generate a DOT syntax execution graph for an LSB script. 160 | 161 | lsb_file should be an LSB file. 162 | If output file is not specified, it defaults to .dot 163 | 164 | The output graph will contain blocks of LSB commands as nodes 165 | and branch points as edges. 166 | """ 167 | path = Path(lsb_file) 168 | print(f"Generating execution graph for {path}") 169 | 170 | if not out_file: 171 | out_file = f"{lsb_file}.dot" 172 | 173 | lsb = LMScript.from_file(path) 174 | dot = nx_to_dot(make_graph(lsb)) 175 | 176 | with open(out_file, "w") as f: 177 | f.write(dot.to_string()) 178 | print(f"Wrote {out_file}") 179 | -------------------------------------------------------------------------------- /src/livemaker/cli/lmlpb.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # -*- coding: utf-8 -*- 19 | """LiveMaker LPB project settings CLI tool.""" 20 | 21 | import shutil 22 | import sys 23 | 24 | import click 25 | 26 | from livemaker.exceptions import BadLpbError, LiveMakerException 27 | from livemaker.lpb import LMProject 28 | from livemaker.lsb.core import Param, ParamType 29 | 30 | from .cli import __version__, _version 31 | from .lmlsb import _edit_parser_op 32 | 33 | 34 | @click.group() 35 | @click.version_option(version=__version__, message=_version) 36 | def lmlpb(): 37 | """Command-line tool for manipulating LPB project settings.""" 38 | pass 39 | 40 | 41 | @lmlpb.command() 42 | @click.argument("input_file", metavar="file", required=True, type=click.Path(exists=True, dir_okay=False)) 43 | def probe(input_file): 44 | """Output information about the specified LPB file in human-readable form.""" 45 | print(input_file) 46 | with open(input_file, "rb") as f: 47 | try: 48 | lpb = LMProject.from_file(f) 49 | except BadLpbError as e: 50 | sys.exit(f"Could not read file: {e}") 51 | print("LiveMaker project settings file:") 52 | print(f" Version: {lpb.version} (LiveMaker{lpb.lm_version})") 53 | print(f" Project Name: {lpb.project_name}") 54 | print(f" Project dir: {lpb.project_dir}") 55 | print(f" Init LSB: {lpb.init_lsb}") 56 | print(f" Exit LSB: {lpb.exit_lsb}") 57 | print(" Settings:") 58 | for setting in lpb.system_settings: 59 | print(" {}: {}".format(setting["name"], setting["value"])) 60 | 61 | 62 | EDITABLE_STRINGS = { 63 | "project_name": "Project Name", 64 | "project_dir": "Project Directory", 65 | "init_lsb": "Initial LSB (run at startup)", 66 | "exit_lsb": "Exit LSB (run at exit)", 67 | } 68 | 69 | 70 | @lmlpb.command() 71 | @click.argument("lpb_file", required=True, type=click.Path(exists=True, dir_okay=False)) 72 | def edit(lpb_file): 73 | """Edit the specified LPB file. 74 | 75 | Only specific settings can be edited. 76 | 77 | The original LPB file will be backed up to .bak 78 | 79 | Note: Setting empty fields to improper data types may cause 80 | undefined behavior in the LiveMaker engine. When editing a field, 81 | the data type of the new value is assumed to be the same as the 82 | original data type. 83 | 84 | """ 85 | with open(lpb_file, "rb") as f: 86 | try: 87 | lpb = LMProject.from_file(f) 88 | except LiveMakerException as e: 89 | sys.exit(f"Could not open LPB file: {e}") 90 | 91 | print(f"Editing LM project {lpb_file}") 92 | for key in lpb.keys(): 93 | orig = getattr(lpb, key) 94 | if key in EDITABLE_STRINGS: 95 | name = EDITABLE_STRINGS[key] 96 | value = click.prompt(name, orig) 97 | if value != orig: 98 | setattr(lpb, key, value) 99 | elif key != "system_settings": 100 | print(f"{key} [{orig}]: ") 101 | print("System settings:") 102 | for setting in lpb.system_settings: 103 | name = setting["name"] 104 | orig = setting["value"] 105 | param_type = setting["type"] 106 | param = Param(value=orig, type=ParamType[param_type]) 107 | _edit_parser_op(param, prompt=f" {name}") 108 | if param.value != orig: 109 | setting["value"] = param.value 110 | print(lpb.system_settings) 111 | 112 | print("Backing up original LPB.") 113 | shutil.copyfile(str(lpb_file), f"{str(lpb_file)}.bak") 114 | try: 115 | new_lpb_data = lpb.to_lpb() 116 | with open(lpb_file, "wb") as f: 117 | f.write(new_lpb_data) 118 | print("Wrote new LPB.") 119 | except LiveMakerException as e: 120 | sys.exit(f"Could not generate new LPB file: {e}") 121 | -------------------------------------------------------------------------------- /src/livemaker/cli/lmpatch.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | """pylivemaker patcher.""" 19 | 20 | import os 21 | import os.path 22 | import shutil 23 | import sys 24 | import tempfile 25 | from pathlib import Path, PureWindowsPath 26 | 27 | import click 28 | from loguru import logger 29 | 30 | from livemaker import LMArchive, LMCompressType 31 | from livemaker.exceptions import LiveMakerException 32 | 33 | from .cli import __version__, _version 34 | 35 | 36 | @click.command() 37 | @click.version_option(version=__version__, message=_version) 38 | @click.argument("archive_file", required=True, type=click.Path(exists=True, dir_okay=False)) 39 | @click.argument("patched_lsb", required=True, type=click.Path(exists=True, dir_okay=True)) 40 | @click.option("--split", is_flag=True, default=False, help="Generate a split data archive.") 41 | @click.option("--no-backup", is_flag=True, default=False, help="Do not generate backup of original archive file(s).") 42 | @click.option( 43 | "-f", "--force", is_flag=True, default=False, help="Overwrite any existing files instead of erroring out." 44 | ) 45 | @click.option("-r", "--recursive", is_flag=True, default=False, help="Patch multiple files into the archive.") 46 | def lmpatch(archive_file, patched_lsb, split, no_backup, force, recursive): 47 | """Patch a LiveMaker game. 48 | 49 | If patched_lsb is a file, any existing version of patched_lsb will be replaced in the specified 50 | LiveMaker archive. If a file with the same name as patched_lsb does 51 | not already exist, this will do nothing. 52 | 53 | If patched_lsb is a directory, every file existing in this directory and in the old archive will 54 | be replaced in the specified LiveMaker archive. 55 | The -r/--recursive option is required for directory mode. 56 | 57 | If file extension for archive_file is ".ext", or if archive_file is not 58 | an excutable and the --split option is specified, 59 | a split archive will be generated. 60 | 61 | A backup copy of the old archive file(s) will also be created unless the 62 | --no-backup option is specified. 63 | 64 | """ 65 | logger.add("patch.log", level="INFO", encoding="utf-8") 66 | 67 | archive_path = Path(archive_file).resolve() 68 | archive_dir = archive_path.parent 69 | archive_name = archive_path.name 70 | 71 | try: 72 | orig_lm = LMArchive(archive_path) 73 | except LiveMakerException as e: 74 | logger.error(e) 75 | return 76 | 77 | if orig_lm.is_exe: 78 | fd, tmp_exe = tempfile.mkstemp() 79 | fp = os.fdopen(fd, "wb") 80 | fp.write(orig_lm.read_exe()) 81 | fp.close() 82 | else: 83 | if orig_lm.is_split: 84 | split = True 85 | tmp_exe = None 86 | 87 | if not no_backup: 88 | backup_paths = {archive_path: Path(f"{archive_path}.bak")} 89 | if orig_lm.is_split: 90 | for p in orig_lm._split_files: 91 | backup_paths[Path(p)] = Path(f"{p}.bak") 92 | for p in backup_paths.values(): 93 | if p.exists(): 94 | if force: 95 | print(f"{p} will be overwritten") 96 | else: 97 | sys.exit(f"{p} already exists") 98 | 99 | if Path(patched_lsb).is_dir(): 100 | if not recursive: 101 | sys.exit(f"Cannot patch directory ({patched_lsb}) without -r/--recursive mode") 102 | else: 103 | if recursive: 104 | sys.exit(f"Cannot patch file ({patched_lsb}) within -r/--recursive mode") 105 | 106 | try: 107 | tmpdir = tempfile.mkdtemp() 108 | tmpdir_path = Path(tmpdir) 109 | logger.info(f"Using temp directory {tmpdir_path}") 110 | print("Generating new archive contents...") 111 | with LMArchive( 112 | name=tmpdir_path.joinpath(archive_name), mode="w", version=orig_lm.version, exe=tmp_exe, split=split 113 | ) as new_lm: 114 | 115 | def bar_show(item): 116 | width, _ = shutil.get_terminal_size() 117 | width //= 4 118 | name = item.name if item is not None else "" 119 | if len(name) > width: 120 | name = "".join(["...", name[-width:]]) 121 | return name 122 | 123 | # patch 124 | with click.progressbar(orig_lm.infolist(), item_show_func=bar_show) as bar: 125 | for info in bar: 126 | # replace existing with patch version 127 | # 128 | # TODO: support writing encrypted files 129 | if info.compress_type == LMCompressType.ENCRYPTED: 130 | compress_type = LMCompressType.NONE 131 | elif info.compress_type == LMCompressType.ENCRYPTED_ZLIB: 132 | compress_type = LMCompressType.ZLIB 133 | else: 134 | compress_type = info.compress_type 135 | 136 | if recursive: 137 | lsb_path = Path(patched_lsb).joinpath(info.path) 138 | if not lsb_path.exists(): 139 | lsb_path = None 140 | else: 141 | lsb_path = Path(patched_lsb) 142 | if info.path != PureWindowsPath(lsb_path): 143 | lsb_path = None 144 | 145 | if lsb_path: 146 | new_lm.write(lsb_path, compress_type=compress_type, unk1=info.unk1, arcname=info.path) 147 | logger.info(f"patched {info.path}") 148 | else: 149 | # copy original version 150 | data = orig_lm.read(info, decompress=False) 151 | new_lm.writebytes(info, data) 152 | logger.info(f"copied {info.path}") 153 | 154 | orig_lm.close() 155 | 156 | # copy temp dir contents to output path then remove the temp dir 157 | # this operation needs to be a copy instead of rename (move) 158 | # in case windows system temp directory is on a different 159 | # logical drive than the output path 160 | print("Writing new archive files...") 161 | for root, dirs, files in os.walk(tmpdir_path): 162 | for name in files: 163 | tmp_p = Path(root).joinpath(name) 164 | orig_p = archive_dir.joinpath(name) 165 | if orig_p.exists() and not no_backup: 166 | orig_p.rename(backup_paths[orig_p]) 167 | shutil.copy(tmp_p, archive_dir) 168 | except Exception as e: 169 | logger.error(e) 170 | 171 | raise e 172 | finally: 173 | if tmp_exe is not None: 174 | Path(tmp_exe).unlink() 175 | 176 | print("Cleaning up temporary files...") 177 | if tmpdir_path: 178 | for root, dirs, files in os.walk(tmpdir_path): 179 | for name in files: 180 | p = Path(root).joinpath(name) 181 | p.unlink() 182 | tmpdir_path.rmdir() 183 | 184 | 185 | if __name__ == "__main__": 186 | lmpatch() 187 | -------------------------------------------------------------------------------- /src/livemaker/exceptions.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2019 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | """pylivemaker exceptions.""" 19 | 20 | 21 | class LiveMakerException(Exception): 22 | """Base pylivemaker exception.""" 23 | 24 | 25 | class BadLiveMakerArchive(LiveMakerException): 26 | """Error raised for bad/invalid archive files.""" 27 | 28 | 29 | class UnsupportedLiveMakerVersion(LiveMakerException): 30 | pass 31 | 32 | 33 | class UnsupportedLiveMakerCompression(LiveMakerException): 34 | pass 35 | 36 | 37 | class BadLsbError(LiveMakerException): 38 | """Error raised for bad/invalid LSB script files.""" 39 | 40 | 41 | class BadLpbError(LiveMakerException): 42 | """Error raised for bad/invalid LPB project settings files.""" 43 | 44 | 45 | class BadLnsError(LiveMakerException): 46 | """Error raised for bad/invalid LNS novel scripts.""" 47 | 48 | 49 | class InvalidCharError(BadLnsError): 50 | def __init__(self, ch, encoding="cp932"): 51 | self.ch = ch 52 | self.encoding = encoding 53 | super().__init__(f"'{ch}' is not a valid {encoding.upper()} character.") 54 | 55 | 56 | class BadTextIdentifierError(LiveMakerException): 57 | """Error raised for bad/invalid translatable text IDs.""" 58 | -------------------------------------------------------------------------------- /src/livemaker/lpb.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | """LiveMaker project settings file (LPB) module.""" 19 | 20 | from io import IOBase 21 | 22 | import construct 23 | import numpy 24 | 25 | from .exceptions import BadLpbError 26 | from .lsb.core import ParamType 27 | from .lsb.lmscript import DEFAULT_LSB_VERSION, LsbVersionValidator, lsb_to_lm_ver 28 | 29 | 30 | class LMProject: 31 | """Class for handling project settings files. 32 | 33 | RE'd from TProject, TProjectSettings classes in LiveMaker code. 34 | 35 | """ 36 | 37 | def __init__( 38 | self, 39 | version=DEFAULT_LSB_VERSION, 40 | project_name="", 41 | unk1=0, 42 | unk2=0, 43 | init_lsb="", 44 | exit_lsb="", 45 | project_dir="", 46 | unk3=0, 47 | bool1=0, 48 | bool2=0, 49 | audio_formats=".wav.wma.ogg.mid.mp3", 50 | bool3=0, 51 | bool4=0, 52 | bool5=1, 53 | insert_disk_prompt="", 54 | exit_prompt="", 55 | system_settings=[], 56 | **kwargs, 57 | ): 58 | self.version = version 59 | self.project_name = project_name 60 | self.unk1 = unk1 61 | self.unk2 = unk2 62 | self.init_lsb = init_lsb 63 | self.exit_lsb = exit_lsb 64 | self.project_dir = project_dir 65 | self.unk3 = unk3 66 | self.bool1 = bool1 67 | self.bool2 = bool2 68 | self.audio_formats = audio_formats 69 | self.bool3 = bool3 70 | self.bool4 = bool4 71 | self.bool5 = bool5 72 | self.insert_disk_prompt = insert_disk_prompt 73 | self.exit_prompt = exit_prompt 74 | if isinstance(system_settings, construct.ListContainer): 75 | settings = [] 76 | for setting in system_settings: 77 | settings.append({"name": setting.name, "type": str(setting.type), "value": setting.value}) 78 | self.system_settings = settings 79 | else: 80 | self.system_settings = system_settings 81 | 82 | def __iter__(self): 83 | return iter(self.items()) 84 | 85 | def __getitem__(self, key): 86 | if key in self.keys(): 87 | return getattr(self, key) 88 | raise KeyError 89 | 90 | def keys(self): 91 | return [ 92 | "version", 93 | "project_name", 94 | "unk1", 95 | "unk2", 96 | "init_lsb", 97 | "exit_lsb", 98 | "project_dir", 99 | "unk3", 100 | "bool1", 101 | "bool2", 102 | "audio_formats", 103 | "bool3", 104 | "bool4", 105 | "bool5", 106 | "insert_disk_prompt", 107 | "exit_prompt", 108 | "system_settings", 109 | ] 110 | 111 | def items(self): 112 | return [(k, self[k]) for k in self.keys()] 113 | 114 | @property 115 | def lm_version(self): 116 | return lsb_to_lm_ver(self.version) 117 | 118 | @classmethod 119 | def _struct(cls): 120 | return construct.Struct( 121 | "version" / LsbVersionValidator(construct.Int32ul), 122 | "project_name" / construct.PascalString(construct.Int32ul, "cp932"), 123 | "unk1" / construct.Int64ul, 124 | "unk2" / construct.Int64ul, 125 | "init_lsb" / construct.PascalString(construct.Int32ul, "cp932"), 126 | "exit_lsb" 127 | / construct.If( 128 | construct.this.version > 0x6D, 129 | construct.PascalString(construct.Int32ul, "cp932"), 130 | ), 131 | "project_dir" / construct.PascalString(construct.Int32ul, "cp932"), 132 | "unk3" / construct.Int32ul, 133 | "bool1" / construct.Byte, 134 | "bool2" 135 | / construct.If( 136 | construct.this.version >= 0x6A, 137 | construct.Byte, 138 | ), 139 | "audio_formats" 140 | / construct.If( 141 | construct.this.version >= 0x6D, 142 | construct.PascalString(construct.Int32ul, "cp932"), 143 | ), 144 | "bool3" 145 | / construct.If( 146 | construct.this.version >= 0x71, 147 | construct.Byte, 148 | ), 149 | "bool4" 150 | / construct.If( 151 | construct.this.version >= 0x72, 152 | construct.Byte, 153 | ), 154 | "bool5" 155 | / construct.If( 156 | construct.this.version >= 0x74, 157 | construct.Byte, 158 | ), 159 | "insert_disk_prompt" / construct.PascalString(construct.Int32ul, "cp932"), 160 | "exit_prompt" / construct.PascalString(construct.Int32ul, "cp932"), 161 | "system_settings" 162 | / construct.PrefixedArray( 163 | construct.Int32ul, 164 | construct.Struct( 165 | "type" / construct.Enum(construct.Byte, ParamType), 166 | "name" / construct.PascalString(construct.Int32ul, "cp932"), 167 | "value" 168 | / construct.Switch( 169 | construct.this.type, 170 | { 171 | "Int": construct.Int32sl, 172 | "Float": construct.ExprAdapter( 173 | construct.Bytes(10), 174 | lambda obj, ctx: numpy.frombuffer(obj.rjust(16, b"\x00"), dtype=numpy.longdouble), 175 | lambda obj, ctx: numpy.longdouble(obj).tobytes()[-10:], 176 | ), 177 | "Flag": construct.Byte, 178 | "Str": construct.PascalString(construct.Int32ul, "cp932"), 179 | }, 180 | ), 181 | ), 182 | ), 183 | ) 184 | 185 | @classmethod 186 | def from_struct(cls, struct, **kwargs): 187 | """Create an LMProject from the specified struct.""" 188 | if isinstance(struct, construct.Container): 189 | d = dict(struct.items()) 190 | d.update(kwargs) 191 | return cls(**d) 192 | raise NotImplementedError 193 | 194 | @classmethod 195 | def from_file(cls, infile): 196 | """Parse the specified file into an LMProject. 197 | 198 | Args: 199 | infile: Input .lpb file. Can be string, path-like or file-like object. 200 | 201 | """ 202 | if not isinstance(infile, IOBase): 203 | infile = open(infile, "rb") 204 | try: 205 | return cls.from_struct(cls._struct().parse_stream(infile)) 206 | except construct.ConstructError as e: 207 | raise BadLpbError(e) 208 | 209 | def to_lpb(self): 210 | """Compile settings into binary .lpb format.""" 211 | try: 212 | return self._struct().build(self) 213 | except construct.ConstructError as e: 214 | raise BadLpbError(e) 215 | -------------------------------------------------------------------------------- /src/livemaker/lpm.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | """LiveMaker preview menu file (LPM) module.""" 19 | 20 | from io import IOBase 21 | 22 | import construct 23 | 24 | from .exceptions import LiveMakerException 25 | 26 | DEFAULT_LPM_VERSION = 106 27 | 28 | 29 | class BadLPMError(LiveMakerException): 30 | pass 31 | 32 | 33 | class _LPMVersionAdapter(construct.Adapter): 34 | def _decode(self, obj, ctx, path): 35 | return int(obj) 36 | 37 | def _encode(self, obj, ctx, path): 38 | return f"{obj:03}".encode("ascii") 39 | 40 | 41 | class LPMVersionValidator(construct.Validator): 42 | def _validate(self, obj, ctx, path): 43 | return obj >= 100 44 | 45 | def _decode(self, obj, ctx, path): 46 | if not self._validate(obj, ctx, path): 47 | raise construct.ValidationError(f"Unsupported LPM version: {obj}") 48 | return obj 49 | 50 | 51 | class LMLivePrevMenu: 52 | """Class for handling preview menu files. 53 | 54 | RE'd from TLivePrevMenu, TLivePrevImages classes in LiveMaker code. 55 | 56 | """ 57 | 58 | def __init__(self, version=DEFAULT_LPM_VERSION, unk1=0, buttons=[], **kwargs): 59 | self.version = version 60 | self.unk1 = unk1 61 | self.buttons = buttons 62 | 63 | def __iter__(self): 64 | return iter(self.items()) 65 | 66 | def __getitem__(self, key): 67 | if key in self.keys(): 68 | return getattr(self, key) 69 | raise KeyError 70 | 71 | def keys(self): 72 | return [ 73 | "version", 74 | "project_name", 75 | "unk1", 76 | "buttons", 77 | ] 78 | 79 | def items(self): 80 | return [(k, self[k]) for k in self.keys()] 81 | 82 | @classmethod 83 | def _struct(cls): 84 | return construct.Struct( 85 | "signature" / construct.Const(b"LivePrevMenu"), 86 | "version" / LPMVersionValidator(_LPMVersionAdapter(construct.Bytes(3))), 87 | "unk1" / construct.Bytes(8), 88 | "buttons" 89 | / construct.PrefixedArray( 90 | construct.Int32ul, 91 | construct.Struct( 92 | "width" / construct.Int32ul, 93 | "height" / construct.Int32ul, 94 | "src" / construct.PascalString(construct.Int32ul, "cp932"), 95 | "unk2" / construct.Byte, 96 | "name" / construct.PascalString(construct.Int32ul, "cp932"), 97 | "src_selected" / construct.PascalString(construct.Int32ul, "cp932"), 98 | "unk3" / construct.PascalString(construct.Int32ul, "cp932"), 99 | "unk4" / construct.PascalString(construct.Int32ul, "cp932"), 100 | "unk5" 101 | / construct.If( 102 | construct.this._._.version > 100, 103 | construct.PascalString(construct.Int32ul, "cp932"), 104 | ), 105 | "unk6" 106 | / construct.If( 107 | construct.this._._.version > 102, 108 | construct.Struct( 109 | construct.PascalString(construct.Int32ul, "cp932"), 110 | construct.PascalString(construct.Int32ul, "cp932"), 111 | ), 112 | ), 113 | "unk7" / construct.PascalString(construct.Int32ul, "cp932"), 114 | "unk8" / construct.PascalString(construct.Int32ul, "cp932"), 115 | "unk9" / construct.PascalString(construct.Int32ul, "cp932"), 116 | "unk10" 117 | / construct.If( 118 | construct.this._._.version > 101, 119 | construct.Struct( 120 | construct.PascalString(construct.Int32ul, "cp932"), 121 | construct.PascalString(construct.Int32ul, "cp932"), 122 | ), 123 | ), 124 | "unk15" / construct.Int32ul, 125 | "unk16" / construct.Int32ul, 126 | "unk17" / construct.PascalString(construct.Int32ul, "cp932"), 127 | "unk18" 128 | / construct.If( 129 | construct.this._._.version > 103, 130 | construct.Struct( 131 | construct.PascalString(construct.Int32ul, "cp932"), 132 | construct.PascalString(construct.Int32ul, "cp932"), 133 | construct.PascalString(construct.Int32ul, "cp932"), 134 | construct.PascalString(construct.Int32ul, "cp932"), 135 | construct.PascalString(construct.Int32ul, "cp932"), 136 | construct.Int32ul, 137 | ), 138 | ), 139 | "unk19" 140 | / construct.If( 141 | construct.this._._.version > 104, 142 | construct.PascalString(construct.Int32ul, "cp932"), 143 | ), 144 | "unk20" 145 | / construct.If( 146 | construct.this._._.version > 105, 147 | construct.PascalString(construct.Int32ul, "cp932"), 148 | ), 149 | ), 150 | ), 151 | ) 152 | 153 | @classmethod 154 | def from_struct(cls, struct, **kwargs): 155 | """Create an LMProject from the specified struct.""" 156 | if isinstance(struct, construct.Container): 157 | d = dict(struct.items()) 158 | d.update(kwargs) 159 | return cls(**d) 160 | raise NotImplementedError 161 | 162 | @classmethod 163 | def from_file(cls, infile): 164 | """Parse the specified file into an LMLivePrevMenu. 165 | 166 | Args: 167 | infile: Input .lpm file. Can be string, path-like or file-like object. 168 | 169 | """ 170 | if not isinstance(infile, IOBase): 171 | infile = open(infile, "rb") 172 | try: 173 | return cls.from_struct(cls._struct().parse_stream(infile)) 174 | except construct.ConstructError as e: 175 | raise BadLPMError(e) 176 | 177 | def to_lpm(self): 178 | """Compile settings into binary .lpm format.""" 179 | try: 180 | return self._struct().build(self) 181 | except construct.ConstructError as e: 182 | raise BadLPMError(e) 183 | -------------------------------------------------------------------------------- /src/livemaker/lsb/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2019 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | """LiveMaker LSB/LSC script module.""" 19 | 20 | from .lmscript import LMScript 21 | from .novel import LNSCompiler, LNSDecompiler 22 | 23 | __all__ = ["LMScript", "LNSCompiler", "LNSDecompiler"] 24 | -------------------------------------------------------------------------------- /src/livemaker/lsb/graph.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | """LiveMaker LSB/LSC command execution tree/graph module.""" 19 | 20 | from collections import deque 21 | 22 | import networkx as nx 23 | from loguru import logger 24 | 25 | from ..exceptions import LiveMakerException 26 | from .command import CommandType 27 | 28 | END_COMMANDS = [ 29 | CommandType.Exit, 30 | CommandType.GameLoad, 31 | CommandType.PCReset, 32 | CommandType.Reset, 33 | CommandType.Terminate, 34 | ] 35 | 36 | 37 | def _jump_edges(lsb, pc, cmd): 38 | ref = cmd.get("Page") 39 | calc = str(cmd.get("Calc")) 40 | logger.info(f"{pc}: {cmd}") 41 | 42 | if ref.Page == lsb.call_name: 43 | next_pc = ref.Label 44 | if calc == "0": 45 | # branch never taken 46 | yield (pc, pc + 1), None 47 | elif calc == "1": 48 | # branch always taken 49 | yield (pc, next_pc), None 50 | else: 51 | yield (pc, next_pc), f"If {calc}" 52 | yield (pc, pc + 1), "Else" 53 | 54 | 55 | def handle_jump(graph, unvisited, lsb, pc, cmd, **kwargs): 56 | for edge, cond in _jump_edges(lsb, pc, cmd): 57 | graph.add_edge(*edge, branch=True, cond=cond) 58 | 59 | 60 | def _if_edge(if_pc, case_pc, cmd): 61 | return (if_pc, case_pc + 1), str(cmd) 62 | 63 | 64 | def handle_if(graph, unvisited, lsb, if_pc, if_cmd, return_pc=None): 65 | logger.info(f"{if_pc}: {if_cmd}") 66 | # find if/elseif/else 67 | if_indent = if_cmd.Indent 68 | if_cases = [(if_pc, if_cmd)] 69 | else_case = [] 70 | for pc in range(if_pc + 1, len(lsb.commands)): 71 | cmd = lsb.commands[pc] 72 | if cmd.Indent > if_indent: 73 | continue 74 | if cmd.Indent == if_indent: 75 | if cmd.type == CommandType.Elseif: 76 | if_cases.append((pc, cmd)) 77 | unvisited.remove(pc) 78 | graph.remove_node(pc) 79 | continue 80 | if cmd.type == CommandType.Else: 81 | else_case.append((pc, cmd)) 82 | unvisited.remove(pc) 83 | graph.remove_node(pc) 84 | continue 85 | end_pc = pc 86 | else: 87 | end_pc = return_pc 88 | break 89 | if end_pc is None: 90 | raise LiveMakerException("invalid If/Elseif/Else sequence") 91 | 92 | # add edges and visit if/elseif/else cases 93 | for pc, cmd in if_cases + else_case: 94 | edge, cond = _if_edge(if_pc, pc, cmd) 95 | graph.add_edge(*edge, branch=True, cond=cond) 96 | nested = deque() 97 | for nested_pc in range(pc + 1, len(lsb.commands)): 98 | nested_cmd = lsb.commands[nested_pc] 99 | if nested_cmd.Indent == cmd.Indent + 1: 100 | nested.append(nested_pc) 101 | continue 102 | elif nested_cmd.Indent > cmd.Indent + 1: 103 | continue 104 | break 105 | visit(graph, nested, lsb, return_pc=end_pc) 106 | if not else_case: 107 | graph.add_edge(if_pc, end_pc, branch=True, cond="Else") 108 | 109 | 110 | def handle_while(graph, unvisited, lsb, init_pc, init_cmd, return_pc=None): 111 | logger.info(f"{init_pc}: {init_cmd}") 112 | # find end of loop 113 | while_pc = None 114 | while_cmd = None 115 | loop_pc = None 116 | while_indent = init_cmd.Indent 117 | for pc in range(init_pc + 1, len(lsb.commands)): 118 | cmd = lsb.commands[pc] 119 | if cmd.Indent > while_indent: 120 | continue 121 | if cmd.Indent == while_indent: 122 | if cmd.type == CommandType.While: 123 | while_pc = pc 124 | while_cmd = cmd 125 | unvisited.remove(pc) 126 | graph.remove_node(pc) 127 | continue 128 | elif cmd.type == CommandType.WhileLoop: 129 | loop_pc = pc 130 | unvisited.remove(pc) 131 | graph.remove_node(pc) 132 | break 133 | raise LiveMakerException("invalid While loop sequence") 134 | 135 | if loop_pc + 1 in unvisited: 136 | end_pc = loop_pc + 1 137 | else: 138 | end_pc = return_pc 139 | if not end_pc: 140 | raise LiveMakerException("invalid While loop sequence") 141 | 142 | # add edges 143 | graph.add_edge(init_pc, while_pc, branch=False) 144 | calc = str(while_cmd.get("Calc")) 145 | graph.add_edge(while_pc, while_pc + 1, branch=True, cond=f"While {calc}") 146 | graph.add_edge(while_pc, end_pc, branch=True, cond="Done") 147 | graph.add_edge(loop_pc, while_pc, branch=False) 148 | 149 | # visit loop 150 | nested = deque(range(while_pc + 1, loop_pc)) 151 | visit(graph, nested, lsb, return_pc=loop_pc) 152 | 153 | 154 | HANDLERS = {CommandType.Jump: handle_jump, CommandType.If: handle_if, CommandType.WhileInit: handle_while} 155 | 156 | 157 | def visit(graph, unvisited, lsb, return_pc=None): 158 | while unvisited: 159 | pc = unvisited.popleft() 160 | cmd = lsb.commands[pc] 161 | if cmd.Mute: 162 | continue 163 | if cmd.type in HANDLERS: 164 | HANDLERS[cmd.type](graph, unvisited, lsb, pc, cmd, return_pc=return_pc) 165 | elif cmd.type not in END_COMMANDS: 166 | if pc + 1 in unvisited: 167 | graph.add_edge(pc, pc + 1, branch=False) 168 | elif return_pc is not None: 169 | graph.add_edge(pc, return_pc, branch=True) 170 | 171 | 172 | def make_graph(lsb): 173 | graph = nx.DiGraph() 174 | 175 | # populate nodes 176 | for i, cmd in enumerate(lsb.commands): 177 | graph.add_node(i, cmd=cmd) 178 | 179 | # find edges 180 | unvisited = deque([i for i in range(len(lsb.commands)) if lsb.commands[i].Indent == 0]) 181 | visit(graph, unvisited, lsb) 182 | return graph 183 | 184 | 185 | def nx_to_dot(graph): 186 | import pydot 187 | 188 | dot = pydot.Dot(graph_type="digraph") 189 | block_nodes = [] 190 | for n in reversed(list(nx.dfs_postorder_nodes(graph))): 191 | block_nodes.append(n) 192 | adjacent = list(graph.adj[n].items()) 193 | if len(adjacent) == 1: 194 | nbr, edge_data = adjacent[0] 195 | cmd = graph.nodes[nbr]["cmd"] 196 | if not (cmd.type == CommandType.Label or edge_data.get("branch")): 197 | continue 198 | 199 | lines = [] 200 | for node in block_nodes: 201 | cmd = graph.nodes[node]["cmd"] 202 | s = str(cmd).replace("\r", "\\r").replace("\n", "\\n") 203 | lines.append(f"{cmd.LineNo:4}: {s}\\l") 204 | if cmd.type == CommandType.TextIns: 205 | blocks = cmd["Text"].get_text_blocks() 206 | if blocks: 207 | for line in blocks[0].text.splitlines(): 208 | lines.append(f" {line}\\l") 209 | lines.append(" ...\\l") 210 | 211 | block_start = block_nodes[0] 212 | dot_node = pydot.Node(block_start, label="".join(lines), shape="box") 213 | dot.add_node(dot_node) 214 | block_nodes.clear() 215 | 216 | for nbr, edge_data in adjacent: 217 | cond = edge_data.get("cond") 218 | if cond: 219 | dot_edge = pydot.Edge(block_start, nbr, label=cond) 220 | else: 221 | dot_edge = pydot.Edge(block_start, nbr) 222 | dot.add_edge(dot_edge) 223 | 224 | return dot 225 | -------------------------------------------------------------------------------- /src/livemaker/lsb/translate.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | """pylivemaker translatable text module.""" 19 | 20 | from abc import ABC 21 | from hashlib import blake2b 22 | 23 | from funcy import cached_property 24 | 25 | from ..exceptions import BadTextIdentifierError, InvalidCharError 26 | 27 | 28 | class BaseTranslatable(ABC): 29 | """Base class for translatable text objects.""" 30 | 31 | def __init__(self, text): 32 | self._orig_text = "\n".join(text.splitlines()) 33 | self.text = self._orig_text 34 | 35 | @property 36 | def orig_text(self): 37 | return self._orig_text 38 | 39 | @property 40 | def text(self): 41 | return "\n".join(self._text) 42 | 43 | @text.setter 44 | def text(self, text): 45 | for ch in text: 46 | try: 47 | ch.encode("cp932") 48 | except UnicodeEncodeError: 49 | raise InvalidCharError(ch) 50 | self._text = text.splitlines() 51 | 52 | @cached_property 53 | def digest(self): 54 | return self.text_digest(self.orig_text) 55 | 56 | def __str__(self): 57 | return str(self.text) 58 | 59 | def __hash__(self): 60 | return hash(self.digest) 61 | 62 | def __eq__(self, other): 63 | return hash(self) == hash(other) 64 | 65 | @staticmethod 66 | def text_digest(text): 67 | hash_ = blake2b(digest_size=8) 68 | hash_.update(text.encode("utf-8")) 69 | return hash_.hexdigest() 70 | 71 | 72 | class BaseTextIdentifier: 73 | """Base identifier for translatable text inside an LSB.""" 74 | 75 | type = "base" 76 | 77 | def __init__(self, filename, line_no, name=""): 78 | self.filename = filename 79 | self.line_no = int(line_no) 80 | self.name = name 81 | 82 | def __hash__(self): 83 | return hash(self.parts) 84 | 85 | def __eq__(self, other): 86 | return hash(self) == hash(other) 87 | 88 | def __str__(self): 89 | return ":".join([str(x) for x in self.parts]) 90 | 91 | @property 92 | def _parts(self): 93 | return [] 94 | 95 | @property 96 | def parts(self): 97 | return ("pylm", self.type, self.filename, self.line_no, *self._parts) 98 | 99 | @classmethod 100 | def from_string(cls, string, **kwargs): 101 | parts = string.split(":") 102 | if len(parts) < 2 or parts[0] != "pylm": 103 | raise BadTextIdentifierError(f"{string} is not a valid pylm identifier") 104 | if parts[1] != cls.type: 105 | raise BadTextIdentifierError(f"{string} is not a valid pylm:{cls.type} identifier") 106 | try: 107 | return cls(*parts[2:], **kwargs) 108 | except ValueError as e: 109 | raise BadTextIdentifierError(f"{string} is not a valid pylm:{cls.type} identifier: {e}") 110 | 111 | 112 | class TextBlockIdentifier(BaseTextIdentifier): 113 | """Identifier for scenario text block.""" 114 | 115 | type = "text" 116 | 117 | def __init__(self, filename, line_no, block_index, **kwargs): 118 | super().__init__(filename, line_no, **kwargs) 119 | self.block_index = int(block_index) 120 | 121 | @property 122 | def _parts(self): 123 | return (self.block_index,) 124 | 125 | 126 | class BaseMenuIdentifier(BaseTextIdentifier): 127 | """Base identifier for selection menus.""" 128 | 129 | type = "menu" 130 | 131 | def __init__(self, filename, line_no, choice_index, **kwargs): 132 | super().__init__(filename, line_no, **kwargs) 133 | self.choice_index = int(choice_index) 134 | 135 | @property 136 | def _parts(self): 137 | return (self.choice_index,) 138 | 139 | 140 | class TextMenuIdentifier(BaseMenuIdentifier): 141 | """Identifier for text selection menu.""" 142 | 143 | type = "menu-text" 144 | 145 | 146 | class LPMMenuIdentifier(BaseMenuIdentifier): 147 | """Identifier for text selection menu.""" 148 | 149 | type = "menu-lpm" 150 | 151 | 152 | supported_identifiers = [ 153 | TextBlockIdentifier, 154 | TextMenuIdentifier, 155 | LPMMenuIdentifier, 156 | ] 157 | 158 | 159 | def make_identifier(string): 160 | for cls in supported_identifiers: 161 | try: 162 | return cls.from_string(string) 163 | except BadTextIdentifierError: 164 | pass 165 | raise BadTextIdentifierError(f"{string} is not a valid pylm identifier") 166 | -------------------------------------------------------------------------------- /src/livemaker/project.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | """pylivemaker project management module.""" 19 | 20 | import os 21 | from collections import defaultdict 22 | from pathlib import Path, PureWindowsPath 23 | 24 | from .exceptions import LiveMakerException 25 | from .lsb.command import CommandType 26 | from .lsb.lmscript import LMScript 27 | 28 | 29 | class PylmProject: 30 | def __init__(self, path): 31 | self.root = self.find_root(path) 32 | if not self.root: 33 | raise LiveMakerException(f"{path} is not inside a LM project") 34 | self._label_cache = defaultdict(dict) 35 | 36 | @staticmethod 37 | def find_root(path): 38 | """Return root LM project dir for the specified path.""" 39 | path = Path(path).resolve() 40 | if not path.exists(): 41 | return None 42 | if path.is_file(): 43 | path = path.parent 44 | search_names = {"live.lpb", "ゲームメイン.lsb", "ノベルシステム"} 45 | for search_dir in [path] + list(reversed(path.parents)): 46 | if set(os.listdir(search_dir)) & search_names: 47 | return search_dir 48 | return None 49 | 50 | def call_name(self, path): 51 | path = Path(path).resolve() 52 | if path.suffix not in (".lsb", ".lsc"): 53 | raise LiveMakerException(f"{path} is not an LSB") 54 | try: 55 | relpath = PureWindowsPath(path.relative_to(self.root)) 56 | name = f"{relpath.stem}.lsb" 57 | return str(relpath.parent / name) 58 | except ValueError: 59 | raise LiveMakerException(f"{path} is outside this project") 60 | 61 | def update_labels(self, lsb): 62 | """Update labels from the specified lsb.""" 63 | if not lsb.call_name: 64 | raise LiveMakerException("Cannot update labels for lsb without call_name") 65 | names = {} 66 | lines = {} 67 | for cmd in lsb.commands: 68 | if cmd.type == CommandType.Label: 69 | name = cmd["Name"] 70 | names[name] = cmd.LineNo 71 | lines[cmd.LineNo] = name 72 | cache = self._label_cache[lsb.call_name] 73 | if "names" in cache: 74 | cache["names"].update(names) 75 | else: 76 | cache["names"] = names 77 | if "lines" in cache: 78 | cache["lines"].update(lines) 79 | else: 80 | cache["lines"] = lines 81 | 82 | def resolve_label(self, ref): 83 | """Return tuple(line_no, name) for the specified label reference.""" 84 | # Page is a rel path with windows slash, we need to convert it to 85 | # system path on posix 86 | if isinstance(ref.Label, int) and ref.Label == 0: 87 | # start of script is not labeled 88 | return None, None 89 | 90 | path = Path(PureWindowsPath(ref.Page)) 91 | call_name = self.call_name(path) 92 | if call_name not in self._label_cache: 93 | path = self.root / PureWindowsPath(call_name) 94 | try: 95 | lsb = LMScript.from_file(path, call_name=call_name) 96 | self.update_labels(lsb) 97 | except LiveMakerException: 98 | raise LiveMakerException(f"Could not update labels from {call_name}") 99 | cache = self._label_cache[call_name] 100 | if isinstance(ref.Label, int): 101 | name = cache["lines"].get(ref.Label) 102 | if name: 103 | return (ref.Label, name) 104 | else: 105 | line_no = cache["names"].get(ref.Label) 106 | if line_no is not None: 107 | return (line_no, ref.Label) 108 | return None, None 109 | -------------------------------------------------------------------------------- /src/livemaker/scramble.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2019 Peter Rowlands 3 | # Copyright (C) 2014 tinfoil 4 | # 5 | # This file is a part of pylivemaker. 6 | # 7 | # This program is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | """LiveMaker scramble (encryption) module.""" 19 | 20 | import math 21 | import struct 22 | 23 | import numpy as np 24 | 25 | # constant XOR key for LM3 26 | LIVEMAKER3_SCRAMBLE_KEY = 0xF8EA 27 | 28 | 29 | class LMScramble: 30 | """PRNG used for LM TScramble encryption. 31 | 32 | RE'd from TScrambleInts, TGetRandom classes in LM3 code. 33 | """ 34 | 35 | # constants for LM3 36 | FACTORS = ( 37 | 0x7DD4FFC7, 38 | 0x000005D4, 39 | 0x000006F0, 40 | 0x000013FB, 41 | ) 42 | 43 | def __init__(self, seed=0): 44 | if seed == 0: 45 | seed = 0xFFFFFFFF 46 | seed = np.uint32(seed & 0xFFFFFFFF) 47 | self.seed = seed 48 | self.state = [] 49 | for i in range(5): 50 | seed ^= seed << np.uint32(13) & 0xFFFFFFFF 51 | seed ^= seed >> np.uint32(17) & 0xFFFFFFFF 52 | seed ^= seed << np.uint32(5) & 0xFFFFFFFF 53 | self.state.append(np.uint32(seed)) 54 | for i in range(19): 55 | self.rand() 56 | 57 | def rand(self): 58 | """Return a random integer in the range [0, uint32_max).""" 59 | x = np.sum( 60 | [ 61 | np.multiply(np.uint64(self.state[3]), self.FACTORS[0], dtype=np.uint64), 62 | np.multiply(self.state[2], self.FACTORS[1], dtype=np.uint64), 63 | np.multiply(self.state[1], self.FACTORS[2], dtype=np.uint64), 64 | np.multiply(self.state[0], self.FACTORS[3], dtype=np.uint64), 65 | self.state[4], 66 | ], 67 | dtype=np.uint64, 68 | ) 69 | self.state[4] = np.uint32((x >> np.uint64(32)) & np.uint64(0xFFFFFFFF)) 70 | self.state[3] = self.state[2] 71 | self.state[2] = self.state[1] 72 | self.state[1] = self.state[0] 73 | self.state[0] = np.uint32(x & np.uint64(0xFFFFFFFF)) 74 | return self.state[0] 75 | 76 | def random(self): 77 | """Return a random float in the range [0.0, 1.0).""" 78 | return self.rand() / 0x100000000 79 | 80 | def randint(self, low, high): 81 | """Return a random integer in the range [low, high].""" 82 | if low > high: 83 | raise ValueError("invalid range") 84 | return low + int(self.random() * (high - low + 1)) 85 | 86 | @classmethod 87 | def randseq(self, count, seed): 88 | """Generate a random sequence of the specified length.""" 89 | scramble = LMScramble(seed) 90 | values = list(range(count)) 91 | seq = [0] * count 92 | i = 0 93 | while values: 94 | if len(values) == 1: 95 | n = 0 96 | else: 97 | n = scramble.randint(0, len(values) - 2) 98 | seq[values.pop(n)] = i 99 | i += 1 100 | return seq 101 | 102 | 103 | def decrypt(data): 104 | """Unscramble the specified data stream and return the result.""" 105 | if len(data) < 8: 106 | return data 107 | chunk_size, seed = struct.unpack("