【Pythonでコード難読化】PyArmorドキュメント（翻訳）

Pythonコードの難読化を行うためPyArmorを使用するにあたりもろもろの調査を行うため、内部用にドキュメント（PyArmor7.5)の翻訳をしました。
 
試用版を使って動かしてみる際は、[インストールとアンインストール](#2インストールとアンインストール)及び[PyArmorの使用](#3pyarmorの使用)を参照すると良いかと思います。

PyArmorで難読化した際に問題が発生したら、まずドキュメントの [When Things Go Wrong（うまくいかないとき）](#16うまくいかないとき)を参照すると解決に役に立ちます。

PyArmorの内部処理を知りたい場合は、[PyArmorの仕組み](#10pyarmorの仕組み)を参照することをお勧めします。

目的に応じて各章を参照ください。

+ まずはインストールして試してみたい
[２.インストールとアンインストール](#2インストールとアンインストール)　及び　[３.PyArmorの使用](#3pyarmorの使用)
+ ちょと高度なテクニックを知りたい
[4.アドバンストピックス](#4アドバンストピックス) 及び [5.Pyarmored Wheelをビルドする](#5pyarmored-wheelをビルドする)
+ 簡単なサンプルを見てみたい
[6.事例紹介](#6事例紹介)
+ プロジェクト化してコード管理をしっかりとしたい
[7.Projectの活用](#7projectの活用)
+ コマンド及びコマンドのオプションを知りたい
[8.Manページ](#8manページ)
+ 難読化についてより詳しく知りたい
[9.難読化されたスクリプトを理解する](#9難読化されたスクリプトを理解する) 及び [10.PyArmorの仕組み](#10pyarmorの仕組み)
+ 生成されるランタイムモジュールについて詳しく知りたい
[11.ランタイムモジュールpytransform](#11ランタイムモジュールpytransform)
+ サポートしているプラットフォームを知りたい
[12.Support Platforms](#12support-platforms)
+ Pyarmorの各難読化モードを詳しく知りたい
[13.難読化したスクリプトのモード](#13難読化したスクリプトのモード)
+ Pyarmorの性能、セキュリティについて知りたい
[14.難読化されたスクリプトのパフォーマンス](#14難読化されたスクリプトのパフォーマンス) 及び [15.PyArmorのセキュリティ](#15pyarmorのセキュリティ)
+ 難読化時のトラブルシューティングをしたい
[16.うまくいかないとき](#16うまくいかないとき)
+ ラインセス及び商用版の購入方法を知りたい
[17.ライセンス](#17ライセンス)

[追伸] 
翻訳に誤りがありましたら随時修正してきますのでご指摘いただけると幸いです。

©️2022 NPO法人AI開発推進協会

# <span style="color: blue; ">Pyarmor ドキュメント</span>  
  
バージョン：	PyArmor 7.5  
ホームページ：	https://pyarmor.dashingsoft.com/  
コンタクト：	`pyarmor@163.com`  
著者：	Jondy Zhao  
著作権：	This document has been placed in the public domain.  
  

# 1.はじめに

PyArmorは、Pythonスクリプトの難読化、難読化されたスクリプトの固定マシンへのバインド、難読化されたスクリプトの期限設定に使用するコマンドラインツールです。PyArmorは、以下の方法でPythonスクリプトを保護します。

+ コードオブジェクトを難読化し、定数やリテラル文字列を保護します。
+ 各関数（コードオブジェクト）のco_codeを実行時に難読化します。
+ コードオブジェクトの実行が完了すると同時にフレームのf_localsをクリアします。
+ 難読化されたスクリプトのライセンスファイルを確認しながら実行します。


PyArmorは、Python 2.6、2.7、およびPython3をサポートしています。  

PyArmorはWindows、Mac OS X、およびLinuxに対してテストされています。

PyArmorはFreeBSDやRaspberry Pi, Banana Pi, Orange Pi, TS-4600 / TS-7600などの組み込みプラットフォームで正常に使用されていますが、完全なテストはされていません。

# 2.インストールとアンインストール
PyArmorは通常のPythonパッケージです。アーカイブは[PyPi](https://pypi.org/project/pyarmor/)からダウンロードできますが、利用可能な場合は [pip](https://pip.pypa.io/en/stable/) を使用してインストールする方が簡単です。たとえば、

``` python
pip install pyarmor
```

または新しいバージョンにアップグレードしてください。

``` python
pip install --upgrade pyarmor
```

pyarmor用のWebUIもあります。次のコマンドでインストールしてください。  

``` python
pip install pyarmor-webui
```


## インストールの確認
すべてのプラットフォームで、コマンドpyarmorが実行パスに存在するはずです。これを確認するには、次のコマンドを入力します。  

``` python
pyarmor --version
```

この結果、PyArmor Version X.Y.ZまたはPyArmor Trial Version X.Y.Zが表示されるはずです。

コマンドが見つからない場合は、実行パスに適切なディレクトリが含まれていることを確認してください。

## インストールされているコマンド

完全なインストールでは、次のコマンドが実行パスに配置されます。  

+ `pyarmor` はメインコマンドです。[PyArmorの使用](#3pyarmorの使用)を参照してください。
+ `pyarmor-webui`は、PyArmorのWeb UIを開くために使用します。

完全なインストール（pipによるインストール）を行わなかった場合、これらのコマンドはコマンドとしてインストールされません。しかし、配布フォルダにあるPythonスクリプトを実行することで、以下に説明するすべての関数を実行することができます。pyarmorコマンドに相当するのは、`pyarmor-folder/pyarmor.py` です。

pyarmor-webuiは、pyarmor-folder/webui/server.pyです。

## クリーンアンインストール

以下のファイルが、pyarmorがインストールされた後に作成されます。

``` python
~/.pyarmor/.pyarmor_capsule.zip    (since v6.2.0)
~/.pyarmor/license.lic             (since v5.8.0)
~/.pyarmor/platforms/

{pyarmor-folder}/license.lic       (before v5.8.0)
~/.pyarmor_capsule.zip             (before v6.2.0)

/path/to/project/.pyarmor_config   (if using project)
```

次のコマンドを実行して、クリーンなアンインストールを行います。  

```
pip uninstall pyarmor

rm -rf ~/.pyarmor
rm -rf {pyarmor-folder}            (before v5.8.0)
rm -rf ~/.pyarmor_capsule.zip      (before v6.2.0)

rm /path/to/project/.pyarmor_config
```

:::note
異なるユーザーでログを取る場合、パス `~` は異なる場合があります。
:::

# 3.PyArmorの使用
pyarmorコマンドの構文は次のとおりです。

`pyarmor[コマンド][オプション]`  

## Pythonスクリプトを難読化する

[obfuscate](https://pyarmor.readthedocs.io/en/latest/man.html#obfuscate)コマンドを使用して、Pythonスクリプトを難読化します。最も単純なケースでは、現在のディレクトリを`myscript.py` プログラムの場所に設定して、以下を実行します。

``` python
pyarmor obfuscate myscript.py
```

PyArmorは、myscript.pyと同じフォルダー内の `*.py` すべてを難読化します。

+ `pyarmor_capsule.zip`が存在しない場合はHOMEフォルダに作成します。
+ スクリプトと同じフォルダに `dist` フォルダがなければ作成します。
+ 難読化した myscript.py を `dist` フォルダに書き込みます。
+ スクリプトと同じフォルダーにあるすべての難読化された *.py を `dist` フォルダーに書き込みます。
+ 難読化されたスクリプトの実行に使用されるランタイムファイルを `dist` フォルダにコピーします。

distフォルダに難読化されたスクリプトと必要なファイルがすべて生成されます。  


``` python
dist/
    myscript.py

    pytransform/
        __init__.py
        _pytransform.so/.dll/.dylib
```

[Runtime Package](#ランタイムパッケージ)と呼ばれるエキストラフォルダ `pytransform` は、難読化されたスクリプトを実行するために必要です。  
  
通常、コマンドラインには1つのスクリプトを指定します。それはエントリースクリプトです。 `myscript.py` の中身はこんな感じです。  

``` python
from pytransform import pyarmor_runtime  
pyarmor_runtime()
__pyarmor__(__name__, __file__, b'\x06\x0f...')
```

最初の2行は [Bootstrap Code](#ブートストラップコード) と呼ばれ、エントリースクリプトにのみ含まれています。これらは難読化されたファイルを使用する前に実行する必要があります。他のすべての難読化された `*.py` については、最後の1行だけです。

``` python
__pyarmor__(__name__, __file__, b'\x0a\x02...')
```
難読化したスクリプトを実行します。

``` python
cd dist
python myscript.py
```

<font color=blue>デフォルトでは、エントリースクリプトと同じパスにある *.py のみが難読化されます。サブフォルダ内のすべての *.py を再帰的に難読化するには、次のコマンドを実行します。</font>

``` python
pyarmor obfuscate --recursive myscript.py
```

## 難読化したスクリプトを配布する

出力パス `dist` にあるファイルをすべてエンドユーザーにコピーするだけです。難読化されたスクリプト以外の [ランタイムパッケージ](#ランタイムパッケージ) もエンドユーザに配布する必要があることに注意してください。

PyArmorは `.py` ファイルのみを扱います。パッケージ内にデータファイルやバイナリ拡張子がある場合、それらを手動で `dist` にコピーしてください。

[ランタイムパッケージ](#ランタイムパッケージ)は難読化されたスクリプトを含まないかもしれません。`import pytransform` が機能する場合にのみ、任意のPythonパスに移動できます。

難読化されたスクリプトのセキュリティについては、[PyArmorのセキュリティ](#15pyarmorのセキュリティ) の項を参照してください。

:::note info
PyArmorはランタイムマシンにインストールする必要はありません。
:::

## 難読化されたスクリプトのライセンスを生成する

難読化されたスクリプトの新しい `license.lic` を生成するには、[licenses](https://pyarmor.readthedocs.io/en/latest/man.html#licenses) コマンドを使用します。

難読化されたスクリプトの期限付きのライセンスを生成します。

``` python
pyarmor licenses --expired 2019-01-01 r001
```

PyArmorが新しいライセンスファイルを生成します。

+ HOMEフォルダの.pyarmor_capsule.zipからデータを読み込みます。
+ licenses/r001フォルダにlicense.licを作成します。
+ licenses/r001フォルダにlicense.lic.txtを作成します。

この新しいものを使ってスクリプトを難読化します。

``` python
pyarmor obfuscate --with-license licenses/r001/license.lic myscript.py
```
難読化したスクリプトを実行すると、2019年1月1日以降にエラーが報告されます。

``` python
cd dist
python myscript.py
```

難読化されたスクリプトを固定マシンにバインドするためのライセンスを生成し、まずハードウェア情報を取得します。

``` python
pyarmor hdinfo
```

そして、ハードディスクのシリアル番号とマックアドレスにバインドして新しいライセンスを生成します。

``` python
pyarmor licenses --bind-disk "100304PBN2081SF3NJ5T" --bind-mac "20:c1:d2:2f:a0:96" code-002
```

難読化されたスクリプトを新しいライセンスで実行します。

``` python
pyarmor obfuscate --with-license licenses/code-002/license.lic myscript.py

cd dist/
python myscript.py
```

また、難読化されたスクリプトと一緒に外部ライセンスファイルlicense.licを使用することもできます。外側のライセンスによって、スクリプトを一度難読化し、必要に応じて古いライセンスを上書きする新しいライセンスを生成します。これは従来の方法であり、[アウターライセンスファイルの使用方法](#アウターライセンスファイルの使用方法)を参照してください。

## ライセンスタイプの拡張

難読化されたスクリプトに対して、他のライセンスタイプを拡張するのは簡単です。エントリースクリプトに認証コードを追加するだけです。難読化されたスクリプトはそれ以上変更できないので、スクリプトの中で好きなことを行ってください。この場合、[ランタイムモジュールpytransform](#11ランタイムモジュールpytransform) が役に立ちます。

望ましい方法は、[Using Plugin to Extend License Type（プラグインを使用したライセンスタイプの拡張）](#プラグインを使用したライセンスタイプの拡張) です。利点は、スクリプトを全く変更する必要がないことです。認証コードを分離したスクリプトに記述し、難読化したスクリプトに難読化として注入するだけです。詳しくは、[プラグインとの付き合い方](#プラグインとの付き合い方) を参照してください。

以下はプラグインの例です。
[https://github.com/dashingsoft/pyarmor/tree/master/plugins](https://github.com/dashingsoft/pyarmor/tree/master/plugins)


## 単一モジュールの難読化
1つのモジュールを正確に難読化するには、`--exact` オプションを使用します。

``` python
pyarmor obfuscate --exact foo.py
```
`foo.py` だけが難読化されているので、この難読化されたモジュールをインポートしてください。

``` python
cd dist
python -c "import foo"
```
## パッケージ全体の難読化

以下のコマンドを実行すると、パッケージが難読化されます。

``` python
pyarmor obfuscate --recursive --output dist/mypkg mykpg/__init__.py
```

難読化されたパッケージをインポートします。

``` python
cd dist
python -c "import mypkg"
```

## 難読化されたスクリプトをパックする

難読化されたスクリプトをバンドルにパックするには、コマンド pack を使います。

まず PyInstaller をインストールします。

``` python
pip install pyinstaller
```

カレントディレクトリをプログラム `myscript.py` の場所に設定し、実行します。

``` python
pyarmor pack myscript.py
```

PyArmorはmyscript.pyをパックします。

+ `pyarmor obfuscate` を実行して `myscript.py` を難読化します。
+ `pyinstaller myscipt.py` を実行して `myscript.spec` を作成します。
+ `myscript.spec` を更新し、元のスクリプトを難読化されたものに置き換えます。
+ `pyinstaller myscript.spec` を実行して、難読化されたスクリプトをバンドルします。
+ `dist/myscript` フォルダに、ユーザに配布するバンドルアプリが格納されます。

最終的に実行可能なファイルを実行します。

``` python
dist/myscript/myscript
```

バンドルの期限付きライセンスを生成します。

``` python
pyarmor licenses --expired 2019-01-01 code-003
pyarmor pack --with-license licenses/code-003/license.lic myscript.py

dist/myscript/myscript
```

複雑な場合は、[pack](https://pyarmor.readthedocs.io/en/latest/man.html#pack)コマンドや[難読化スクリプトのパック方法](#9難読化されたスクリプトを理解する)を参照してください。

## さらなるセキュリティの向上

これらの[PyArmor](https://pyarmor.dashingsoft.com/)の機能は、セキュリティをさらに強化する可能性があります。

+ 可能であれば　[Super Plus Mode](#super-plus-mode) を使用してスクリプトを難読化し、そうでなければ [Super Mode](#super-mode) を使用し、プラットフォームがサポートされている場合は [Advanced Mode](#advanced-mode) を有効にします。Windowsで、パフォーマンスが要件を満たしている場合は、[VMモード](#vm-mode)を有効にします。
+ 難読化されたスクリプトが1つの実行形式バンドルとして配布されている場合は、[難読化されたスクリプトをPythonインタプリタにバインドする](#難読化スクリプトのpythonインタプリタへのバインド)を試してみてください。
+ エントリースクリプトに[クロスプロテクションコード](#エントリースクリプトの特殊な取り扱いについて)のパッチが適用されていることを確認し、[クロスプロテクションコードのカスタマイズ](#クロスプロテクションコードのカスタマイズ)を試す。
+ 対応する [Restrict Mode](#restrict-mode) を使って、難読化されたモジュールを誰もインポートできないようにする。
+ 高セキュリティコードの難読化 `-obf-code=2` を使用する。
+ 難読化されたスクリプトにプライベートチェックポイントを挿入することによる [プラグインを使用したセキュリティの向上](#プラグインを使ったセキュリティの向上) では、モンキートリックハッキングを防ぐために使用されるpytransformに1つのデコレータ `assert_armored（）` と1つの関数 `check_armored（）`があります。難読化されたスクリプトがハッキングされないことを確認するために、他のチェックポイントを追加することもできます。
+ データファイルを保護する必要がある場合は、[データファイルの保護方法](#データファイルの保護方法) を参照してください。

難読化されたスクリプトのセキュリティについては、[PyArmorのセキュリティ](#15pyarmorのセキュリティ)を参照してください。

# 4.アドバンストピックス

## スーパーモードの使用
[スーパーモード](#super-mode) はv6.2.0から導入され、難読化されたスクリプトを実行するために必要な拡張モジュールは1つだけとなり、以前は一部のユーザーを混乱させていた [ブートストラップコード](#ブートストラップコード) はなくなり、すべての難読化スクリプトが同じになりました。これにより、セキュリティは格段に向上し、使い方もシンプルになりました。ただし、最新のPythonバージョン2.7, 3.7, 3.8, 3.9 にしか対応していないのが難点です。

オプション `--advanced 2` でsuper modeを有効にすします。　例えば、

``` python
pyarmor obfuscate --advanced 2 foo.py
```

難読化したスクリプトを他のマシンに配布する場合、拡張モジュール`pytransform`がPythonのパスにあれば、難読化したスクリプトは正常に動作するはずです。

難読化されたスクリプトを復元するには、事前に `license.lic` を生成しておく必要があります。たとえば、

``` python
pyarmor licenses --bind-mac xx:xx:xx:xx regcode-01
```

そして、このライセンスを `--with-license` などのオプションで指定します。

``` python
pyarmor obfuscate --with-license licenses/regcode-01/license.lic \
                  --advanced 2 foo.py
```

この方法で、指定されたライセンスファイルは拡張モジュールpytransformに組み込まれます。もし外側の`license.lic` を使いたいなら、例えば `--with-license` オプションを特別な値 `outer` に設定するだけで、簡単に他のライセンスと置き換えることができます。


``` python
pyarmor obfuscate --with-license outer --advanced 2 foo.py
```

詳しくは、次章をご覧ください。

## アウターライセンスファイルの使用方法

v6.3.0以降、ランタイムファイルlicense.licはダイナミックライブラリに組み込まれました。もし、外側の`license.lic` を使用したい場合は、オプション `--with-license` に特別な値 `outer` を設定します。

``` python
pyarmor obfuscate --with-license outer foo.py
```

難読化されたスクリプトが起動すると、`license.lic` を順番に検索します。

1. 環境変数 `PYARMOR_LICENSE` をチェックし、設定されていれば、このファイル名を使用します。
2. `sys.PYARMOR_LICENSE` をチェックし、設定されていれば、このファイル名を使用します。
3. 設定されていない場合は、現在のパスで `license.lic` を検索します。
4. 非スーパーモードでは、ランタイムパッケージ `pytransform` のパスで `license.licを`検索します。
5. それでも見つからない場合は例外を発生させます

これは `sys.PYARMOR_LICENSE` の基本的な使用方法です。

非スーパーモードでは、ランタイムファイル `dist/pytransform/__init__.py` の `pyarmor_runtime` 関数を編集し、一行を追加します。

``` python
sys.PYARMOR_LICENSE = '/path/to/license.lic'
```

スーパーモードでは、pythonの拡張子 `pytransform.so` を同名のパッケージ `pytransform` に変換してください。例えば、

``` python
cd dist
mkdir pytransform
mv pytransform.so pytransform/
```

そして、`dist/pytransform/__init__.py` を作成します。

``` python
import sys
sys.PYARMOR_LICENSE = '/path/to/license.lic'
name = 'pytransform'
m = __import__(name, globals(), locals(), ['*'])
sys.modules[__name__].__dict__.update(m.__dict__)
```

## 多くのパッケージの難読化

pkg1, pkg2, pkg2 の3つのパッケージがあります。これらはすべて難読化され、共有のランタイムファイルを使用する予定です。

まず、作業パスを変更し、3つのプロジェクトを作成します。

``` python
mkdir build
cd build

pyarmor init --src /path/to/pkg1 --entry __init__.py pkg1
pyarmor init --src /path/to/pkg2 --entry __init__.py pkg2
pyarmor init --src /path/to/pkg3 --entry __init__.py pkg3
```

次に、[ランタイムパッケージ](#ランタイムパッケージ) を作成し、パス `dist` に保存します。

``` python
pyarmor build --output dist --only-runtime pkg1
```

または、[ランタイムパッケージ](#ランタイムパッケージ)を直接生成する場合は、[runtime](https://pyarmor.readthedocs.io/en/latest/man.html#runtime)コマンドを実行します。

``` python
pyarmor runtime --output dist
```

次に3つのパッケージを難読化し、`dist` に保存します。

``` python
pyarmor build --output dist --no-runtime pkg1
pyarmor build --output dist --no-runtime pkg2
pyarmor build --output dist --no-runtime pkg3
```

すべての出力を確認し、これらの難読化されたパッケージをテストしてください。

``` python
ls dist/

cd dist
python -c 'import pkg1
import pkg2
import pkg3'
```

:::note info
出力パスdistにあるランタイムパッケージ `pytransform` は、インポートできる場合に限り、他のPythonパスへ移動することができます。

v5.7.2以降では、[ランタイムパッケージ](#ランタイムパッケージ)も別途 [runtime](https://pyarmor.readthedocs.io/en/latest/man.html#runtime) コマンドで生成できるようになりました。

``` python
pyarmor runtime
```
:::

## 他の難読化ライブラリとのコンフリクト解消

:::note info
v5.8.7での新機能
:::

異なる開発者によって難読化された2つのパッケージがあるとして、それらを同じPythonインタープリタでインポートできますか？  

もし両方ともpyarmorの試用版で難読化されているのなら、問題はありません、答えはイエスです。しかし、どちらかが登録版で難読化されている場合、答えは「ノー」です。  

v5.8.7以降では、オプション --enable-suffix でスクリプトを難読化し、[ランタイムパッケージ](#ランタイムパッケージ) を固定名pytransform以外のユニークなサフィックス付きで生成することができます。例えば、

``` python
pyarmor obfuscate --enable-suffix foo.py
```
このような出力になります。

``` python
dist/
    foo.py
    pytransform_vax_000001/
        __init__.py
        ...
```

接尾辞 `_vax_000001` は、PyArmor のレジストレーションコードを元にしたものです。

プロジェクトの場合、[config](https://pyarmor.readthedocs.io/en/latest/man.html#config) コマンドで `enable-suffixを` 設定します。
  
``` python
pyarmor config --enable-suffix 1
pyarmor build -B
```

または、この方法で無効化します。

``` python
pyarmor config --enable-suffix 0
pyarmor build -B
```

## 難読化されたパッケージの配布

配布するパッケージが多い場合は、enableサフィックスをつけた[ランタイムパッケージ](#ランタイムパッケージ)を別途生成し、すべてのパッケージで共有することをお勧めします。

例えば、まずruntimeコマンドで[ランタイムパッケージ](#ランタイムパッケージ)を生成します。

``` python
pyarmor runtime --enable-suffix -O dist/shared
```

出力されたパッケージは `dist/shared/pytransform_vax_000001` のように見えるかもしれません。

各パッケージについて、この共有 `pytransform` で難読化します。

``` python
pyarmor obfuscate --enable-suffix --recursive --bootstrap 2 \
                  -O dist/pkg1 --runtime @dist/shared src/pkg1/__init__.py
```

v6.3.7からの新機能で、`--runtime` オプションが使えない場合は、`--no-runtime` に置き換えてください。

``` python
pyarmor obfuscate --enable-suffix --recursive --bootstrap 2 \
                  -O dist/pkg1 --no-runtime src/pkg1/__init__.py
```

次に、通常のパッケージとして pytransform_vax_000001 パッケージを配布します。

最後に、難読化されたパッケージ `dist/pkg1` を配布し、セットアップスクリプトで依存関係を追加します。例えば、

``` python
install_requires=['pytransform_vax_000001']
```

他のパッケージ pkg2, pkg3 などにも pkg1 と同じことを行ってください。

## 難読化したスクリプトを他のプラットフォームへ配布する

まず、[download](https://pyarmor.readthedocs.io/en/latest/man.html#download)コマンドで利用可能なプラットフォーム名をすべてリストアップします。

``` python
pyarmor download
pyarmor download --help-platform
```

オプション `-list` を指定すると、詳細が表示されます。

``` python
pyarmor download --list
pyarmor download --list windows
pyarmor download --list windows.x86_64
```

そして、スクリプトを難読化する際にプラットフォーム名を指定します。

``` python
pyarmor obfuscate --platform linux.armv7 foo.py

# For project
pyarmor build --platform linux.armv7
```

## 異なる機能を持つスクリプトの難読化

同じプラットフォームで利用可能なダイナミックライブラリは多数存在します。それぞれのライブラリは異なる機能を持っています。例えば、windows.x86_64.0 と windows.x86_64.7 はどちらもwindwos.x86_64というプラットフォームで動作します。最後の数字は、機能を表しています。

+ 0: アンチデバッグ、JIT、アドバンスドモード機能、高速性なし
+ 7: アンチデバッグ、JIT、アドバンストモード機能、高セキュリティを含む

特殊な機能でスクリプトを難読化することが可能です。例えば

``` python
pyarmor obfuscate --platform linux.x86_64.7 foo.py
```

なお、機能の異なるダイナミックライブラリには互換性がありません。例えば、Windowsで `--platform linux.arm.0` でスクリプトを難読化しようとした場合。

``` python
pyarmor obfuscate --platform linux.arm.0 foo.py
```

Windowsのデフォルトのプラットフォームはフル機能の `windows.x86_64.7` なので、PyArmorは`windows.x86_64.0` プラットフォームで再起動し、この低機能プラットフォーム `linux.arm.0` 用に難読化スクリプトを作成しなければなりません。

また、環境変数`PYARMOR_PLATFORM` をターゲットマシンと同じ機能プラットフォームに設定することもできます。例えば、

``` python
PYARMOR_PLATFORM=windows.x86_64.0 pyarmor obfuscate --platform linux.arm.0 foo.py

# In Windows
set PYARMOR_PLATFORM=windows.x86_64.0
pyarmor obfuscate --platform linux.arm.0 foo.py
set PYARMOR_PLATFORM=
```

## 難読化されたスクリプトを複数のプラットフォームで動作させる場合

v5.7.5から、プラットフォーム名が標準化され、利用可能なプラットフォーム名はすべて [Standard Platform Names](https://pyarmor.readthedocs.io/en/latest/platforms.html#standard-platform-names) にリストアップされています。これにより、難読化されたスクリプトを複数のプラットフォームで実行できるようになりました。

複数のプラットフォームに対応するためには、これらのプラットフォーム用のダイナミックライブラリを全て[ランタイムパッケージ](#ランタイムパッケージ)にコピーする必要があります。例えば、難読化したスクリプトは、Windows/Linux/MacOSで動作させることができます。

``` python
pyarmor obfuscate --platform windows.x86_64 \
                  --platform linux.x86_64 \
                  --platform darwin.x86_64 \
                  foo.py
```

[ランタイムパッケージ](#ランタイムパッケージ)も、一度 [runtime](https://pyarmor.readthedocs.io/en/latest/man.html#runtime)コマンドで生成し、ランタイムファイルなしでスクリプトを難読化することができます。たとえば、

``` python
pyarmor runtime --platform windows.x86_64,linux.x86_64,darwin.x86_64
pyarmor obfuscate --no-runtime --recursive \
                  --platform windows.x86_64,linux.x86_64,darwin.x86_64 \
                  foo.py
```

難読化されたスクリプトはダイナミックライブラリをチェックするため、 --no-runtimeオプションを指定した場合でもプラットフォームの指定は必要です。しかし、--no-cross-protectionオプションが指定されている場合、難読化されたスクリプトはダイナミック・ライブラリをチェックしないので、プラットフォームは必要ありません。例えば、

``` python
pyarmor obfuscate --no-runtime --recursive --no-cross-protection foo.py
```

:::note info
プラットフォームが `Windows.x86_64.0` の場合、他のプラットフォームも同じフィーチャーである必要があります。
:::

:::note info
難読化されたスクリプトが他のプラットフォームで動作しない場合は、ダウンロードしたファイルをすべて更新してみてください。

``` python
pyarmor download --update
```
それでもうまくいかない場合は、`$HOME/.pyarmor` パスにあるcahcedプラットフォームファイルを削除してみてください。
:::

## 他のPythonバージョンによるスクリプトの難読化

マシンに複数のバージョンのPythonがインストールされている場合、pyarmorコマンドはデフォルトのPythonを使用します。他のPythonで難読化する必要がある場合は、そのPythonで明示的にpyarmorを実行します。

例えば、まず `pyarmor.py` を探します。

``` python
find /usr/local/lib -name pyarmor.py
```

一般に、ほとんどのlinuxでは、 `/usr/local/lib/python2.7/dist-packages/pyarmor` の中にあるはずです。

次に、以下のようにpyarmorを実行します。

``` python
/usr/bin/python3.6 /usr/local/lib/python2.7/dist-packages/pyarmor/pyarmor.py
```
シェルスクリプト `/usr/local/bin/pyarmor3` を作成すると便利です。内容は、

``` python
/usr/bin/python3.6 /usr/local/lib/python2.7/dist-packages/pyarmor/pyarmor.py "$@"
```

そして、

``` python
chmod +x /usr/local/bin/pyarmor3
```

前回と同様にpyarmor3を使用します。

Windowsで、pyarmor3.batというbatファイルを作成し、内容は以下のようなものになります。

``` python
C:\Python36\python C:\Python27\Lib\site-packages\pyarmor\pyarmor.py %*
```

## プレーンスクリプトでブートストラップコードを実行する

v5.7.0以前では、[ブートストラップコード](#ブートストラップコード) はプレーンスクリプトに直接挿入できましたが、 セキュリティのために、[ブートストラップコード](#ブートストラップコード) は難読化スクリプトの中になければなりません。プレーン・スクリプトの中で[ブートストラップコード](#ブートストラップコード)を実行するために、別の方法が必要です。

まず、[runtime](https://pyarmor.readthedocs.io/en/latest/man.html#runtime)コマンドで1つのブートストラップ・パッケージ pytransform_bootstrap を作成します。

``` python
pyarmor runtime -i
```

次に、bootstrap パッケージをプレーンスクリプトのパスに移動します。

``` python
mv dist/pytransform_bootstrap /path/to/script
```

また、例えば、pythonのシステムライブラリにコピーすることもできます。

``` python
mv dist/pytransform_bootstrap /usr/lib/python3.5/ (For Linux)
mv dist/pytransform_bootstrap C:/Python35/Lib/ (For Windows)
```

次に、プレーンスクリプトを編集し、1行挿入します。

``` python
import pytransform_bootstrap
```

これで、この行の後に他の難読化されたモジュールがインポートできるようになりました。

:::note info
v5.8.1以前は、この方法でブートストラップパッケージを作成してください。

``` python
echo "" > __init__.py
pyarmor obfuscate -O dist/pytransform_bootstrap --exact __init__.py
```

:::

## 難読化されたスクリプトのunittestの実行

難読化されたスクリプトの多くには、[ブートストラップコード](#ブートストラップコード)が存在しません。そのため、難読化されたスクリプトではunittestスクリプトは動作しないかもしれません。

テストスクリプトが /path/to/tests/test_foo.py だとすると、まずこのテストスクリプトにパッチを当て、[プレーンスクリプトでブートストラップコードを実行する](#プレーンスクリプトでブートストラップコードを実行する)よう指示します。

その後、難読化されたモジュールで動作するようになります。

``` python
cd /path/to/tests
python test_foo.py
```
他の方法は、直接システムパッケージunittestにパッチを当てることです。ブートストラップパッケージpytransform_bootstrapがPythonシステムライブラリにコピーされていることを確認してください。[プレーンスクリプトでブートストラップコードを実行する](#プレーンスクリプトでブートストラップコードを実行する)を参照してください。

そして、`/path/to/unittest/__init__.py` を編集して、一行挿入します。

``` python
import pytransform_bootstrap
```

これで、すべてのユニテストスクリプトが難読化されたスクリプトで動作するようになりました。多くのユニテストスクリプトがある場合に有効です。

## Python Interpreter に難読化されたスクリプトを自動的に認識させる

もし Python Interpreter が難読化されたスクリプトを自動的に認識できれば、すべてがシンプルになります。

+ 難読化されたスクリプトのほとんどは、メインスクリプトとして実行されます。
+ 難読化されたスクリプトの中でマルチプロセシングを呼び出し、新しいプロセスを作成します。
+ あるいは Popen や os.exec などを呼び出して、他の難読化されたスクリプトを実行する。
+ ...

以下は基本的なステップです。

１. まず、ブートストラップパッケージ pytransform_bootstrap を作成します。

``` python
pyarmor runtime -i
```
v5.8.1以前は、空のパッケージを難読化することで作成する必要があります。

``` python
echo "" > __init__.py
pyarmor obfuscate -O dist/pytransform_bootstrap --exact __init__.py
```

２. 次に、難読化されたスクリプトを実行するための仮想Python環境を作成し、ブートストラップ・パッケージを仮想Pythonライブラリに移動させます。例えば、

``` python
# For windows
mv dist/pytransform_bootstrap venv/Lib/

# For linux
mv dist/pytransform_bootstrap venv/lib/python3.5/
```
３. venv/lib/site.py または venv/lib/pythonX.Y/site.py を編集し、main行の前に pytransform_bootstrap をインポートしてください。

``` python
import pytransform_bootstrap

if __name__ == '__main__':
    ...
```
また、main関数の末尾に挿入したり、モジュールサイトがインポートされたときに実行できる場所に挿入することもできます。

その後、Pythonの初期化時にモジュールサイトが自動的にインポートされるので、仮想環境上で難読化されたスクリプトを直接実行することができます。

[https://docs.python.org/3/library/site.html](https://docs.python.org/3/library/site.html) を参照してください。

:::note info
この仮想環境ではpyarmorというコマンドは動作せず、難読化されたスクリプトを実行するためにのみ使用されます。
:::

:::note info
v5.7.0以前は、Runtime Filesでbootstrapパッケージを手動で作成する必要があります。
:::

## 異なるモードでのPythonスクリプトの難読化

[Advanced Mode](#advanced-mode)はPyArmor 5.5.0から導入されましたが、デフォルトでは無効になっています。有効にするにはオプション `--advanced` を指定してください。

``` python
pyarmor obfuscate --advanced 1 foo.py

# For project
cd /path/to/project
pyarmor config --advanced 1
pyarmor build -B
```

PyArmor 5.2以降、デフォルトの[Restrict Mode](#restrict-mode)は1ですが、オプション `--restrict` で変更することができます。

``` python
pyarmor obfuscate --restrict=2 foo.py
pyarmor obfuscate --restrict=3 foo.py

# For project
cd /path/to/project
pyarmor config --restrict 4
pyarmor build -B
```

必要であれば、この方法ですべての制限を無効にすることができます。

``` python
pyarmor obfuscate --restrict=0 foo.py

# For project
pyarmor config --restrict=0
pyarmor build -B
```

スクリプトがlicensesによって生成されたライセンスを使用する場合、すべての制限を無効にするには、licensesコマンドにオプション `--disable-restrict-mode` を渡します。例えば、

``` python
pyarmor licenses --disable-restrict-mode r001

pyarmor obfuscate --with-license=licenses/r001/license.lic foo.py

# For project
pyarmor config --with-license=licenses/r001/license.lic
pyarmor build -B
```

[Obfuscating Code Mode](#obfuscating-code-mode), [Wrap Mode](#wrap-mode), [Obfuscating module Mode](#obfuscating-module-mode)は、[obfuscate](https://pyarmor.readthedocs.io/en/latest/man.html#obfuscate)コマンドで変更することができません。これらは、[プロジェクトを活用](#7projectの活用)する際に、[config](https://pyarmor.readthedocs.io/en/latest/man.html#config)コマンドで変更することができます。たとえば、

``` python
pyarmor init --src=src --entry=main.py .
pyarmor config --obf-mod=1 --obf-code=1 --wrap-mode=0
pyarmor build -B
```

## プラグインを使用したライセンスタイプの拡張

PyArmorは難読化されたスクリプトのライセンスタイプをプラグインで拡張することができます。例えば、ローカルタイム以外のインターネットタイムをチェックする場合です。

まず、プラグインスクリプト[check_ntp_time.py](https://github.com/dashingsoft/pyarmor/blob/master/plugins/check_ntp_time.py)を作成します。このスクリプトの主要な関数は`check_ntp_time` で、もう一つの重要な関数は `_get_license_data` で、難読化スクリプトの `license.lic` から追加データを取得するために使用されます。

次に、エントリースクリプト [foo.py](https://github.com/dashingsoft/pyarmor/blob/master/plugins/foo.py) に2つのコメントを挿入します。

``` python
# {PyArmor Plugins}
# PyArmor Plugin: check_ntp_time()
```

エントリースクリプトを難読化します。

``` python
pyarmor obfuscate --plugin check_ntp_time foo.py
```

プラグインファイルがカレントパスにない場合、代わりに絶対パスを使用します。

``` python
pyarmor obfuscate --plugin /usr/share/pyarmor/check_ntp_time foo.py
```

最後に、この難読化されたスクリプトのために1つのライセンスファイルを生成し、オプション `-x` で追加のライセンスデータを渡します。このデータは、プラグインスクリプトの関数 `_get_license_data` で得ることができます。

``` python
pyarmor licenses -x 20190501 rcode-001
pyarmor obfuscate --with-license licenses/rcode-001/license.lic \
                  --plugin check_ntp_time foo.py
```

packコマンド用：

``` python
pyarmor licenses -x 20190501 rcode-001
pyarmor pack --with-license licenses/rcode-001/license.lic \
             -x " --plugin check_ntp_time" foo.py
```

その他の例については、[https://github.com/dashingsoft/pyarmor/tree/master/plugins](https://github.com/dashingsoft/pyarmor/tree/master/plugins) を参照してください。

プラグインの仕組みについては、[プラグインとの付き合い方](#プラグインとの付き合い方)を参照してください。

:::note warn
プラグイン内の出力関数名は、プラグイン名と同じでなければなりません。
:::

## 難読化されたスクリプトを1つの実行ファイルにまとめる
次のコマンドを実行して、スクリプト `foo.py` を一つの実行ファイル `dist/foo.exe` にパックします。ここで、`foo.py` は難読化されていませんが、パックする前に難読化されます。

``` python
pyarmor pack -e " --onefile" foo.py
dist/foo.exe
```

難読化したスクリプトの「license.lic」を実行ファイルにバンドルせず、実行ファイルの外側に配置したい場合は、以下のようにします。

``` python
dist/
    foo.exe
    license.lic
```

これにより、後で簡単にユーザーごとに異なるライセンスを生成することができます。以下は基本的な手順です。


１. まず、`runtime-hook script`を `license.py` をコピーして作成します。

``` python
import sys
from os.path import join, dirname
with open(join(dirname(sys.executable), 'license.lic'), 'rb') as fs:
    with open(join(sys._MEIPASS, 'license.lic'), 'wb') as fd:
        fd.write(fs.read())
```

２. そして、スクリプトに追加オプションを詰めます。

``` python
pyarmor pack --clean --without-license -x " --exclude copy_license.py" \
        -e " --onefile --icon logo.ico --runtime-hook copy_license.py" foo.py
```

オプション `--without-license` は、難読化されたスクリプトの `license.lic` を最終的な実行ファイルにバンドルしないよう指定します。PyInstaller のオプション `--runtime-hook` を指定すると、指定したスクリプト `copy_license.py` が難読化されたスクリプトがインポートされる前に実行されます。このスクリプトは、外側の `license.lic` を正しいパスへコピーします。

`dist/foo.exe` を実行すると、ライセンスエラーが報告されるはずです。

３. 最後に [licenses](https://pyarmor.readthedocs.io/en/latest/man.html#licenses)を実行して難読化したスクリプトの新しいライセンスを生成し、新しいlicense.licとdist/foo.exeをエンドユーザー向けにコピーしてください。

``` python
pyarmor licenses -e 2020-01-01 code-001
cp license/code-001/license.lic dist/

dist/foo.exe
```

## 難読化されたスクリプトをカスタマイズされたspecファイルでバンドルする

カスタマイズされた `.spec` ファイルがある場合、例えば、以下のように動かします。

``` python
pyinstaller myscript.spec
```

[難読化されたスクリプトでpyinstallerバンドルを再パックする](#難読化されたスクリプトでpyinstallerバンドルを再パックする)を参照してください。

または、`-s` オプションで直接難読化し、スクリプトをパックしてください。

``` python
pyarmor pack -s myscript.spec myscript.py
```

このエラーが発生した場合、

``` python
Unsupport .spec file, no XXX found
```

`.spec` ファイルを確認し、トップレベルに2行あることを確認します（identationなし）。

``` python
a = Analysis(...

pyz = PYZ(...
```

そして、Analysisオブジェクトを作成する際には、3つのキーパラメータがあります。例えば、

``` python
a = Analysis(
    ...
    pathex=...,
    hiddenimports=...,
    hookspath=...,
    ...
)
```

PyArmorはこれらの行に必要なオプションを自動的に追加します。しかし、v5.9.6以前は手動でパッチを当てる必要があります。

+ モジュール `pytransform` を `hiddenimports` に追加します
+ DISTPATH/obf/tempのパスをpathexとhookspathに追加します

変更後は以下のようになります。

``` python
a = Analysis(['myscript.py'],
             pathex=[os.path.join(DISTPATH, 'obf', 'temp'), ...],
             binaries=[],
             datas=[],
             hiddenimports=['pytransform', ...],
             hookspath=[os.path.join(DISTPATH, 'obf', 'temp'), ...],
             ...
```

:::note info
この機能は、v5.8.0から導入されました。

v5.8.2以前では、追加パスはDISTPATH/obfであり、DISTPATH/tempではありません。
:::

## 制限モードによるセキュリティの向上

デフォルトでは、難読化されたスクリプトを変更できないように、制限モード1によって難読化されています。この難読化されたスクリプトを、難読化されたスクリプトの外にインポートできないように、制限モード2で難読化することにより、セキュリティを向上させることができます。例えば、

``` python
pyarmor obfuscate --restrict 2 foo.py
```

また、より安全性を高めるために、制限モード3によってスクリプトを難読化することも可能です。さらに、難読化されたスクリプトの中ですべての関数が呼び出されていることを確認するために、各関数の呼び出しをチェックします。例えば、

``` python
pyarmor obfuscate --restrict 3 foo.py
```

しかし、Pythonパッケージにはモード2と3が適用されません。Pythonパッケージのセキュリティを向上させるために、もう一つの解決策があります。

外部スクリプトで使用される `.py` ファイルは、`restrict mode 1` で難読化されます。
パッケージ内でのみ使用される他のすべての.pyファイルは、制限モード4で難読化されます。
例えば、`mypkg` は2つのファイルを含んでいます。

+ `__init__.py`
+ `foo.py`

ここでは、`mypkg/__init__.py` の内容です。

``` python
from .foo import hello

def open_hello(msg):
    print('This is public hello: %s' % msg)

def proxy_hello(msg):
    print('This is proxy hello from foo: %s' % msg)
    hello(msg)
```

さて、このパッケージをこのように難読化します。

``` python
cd /path/to/mypkg
pyarmor obfuscate -O obf/mypkg --exact __init__.py
pyarmor obfuscate -O obf/mypkg --restrict 4 --recursive --exclude __init__.py .
```

ですから、`mypkg` をインポートして、 `__init__.py` の中の任意の関数を呼び出してもOKです。

``` python
cd /path/to/mypkg/obf
python

>>> import mypkg
>>> mypkg.open_hello("it should work")
>>> mypkg.proxy_hello("also OK")
```

しかし、mypkg.fooの中のどの関数を呼んでもうまくいきません。例えば、

``` python
cd /path/to/mypkg/obf
python

>>> import mypkg
>>> mypkg.foo.hello("it should not work")
```

Restrict Modeについて詳しくは、[Restrict Mode](#restrict-mode) を参照してください。

## プラグインを使ったセキュリティの向上

プラグインを使えば、難読化されたスクリプトに任意のプライベートチェックポイントを注入することができ、元のスクリプトには影響を与えません。プラグインとしてコメントされていない場合、難読化されたスクリプトの中で実行される必要があります。

チェック・ロジックは誰にもわからないし、いつでも変更できます。そのため、より安全性が高くなります。例えば、デバッガプロセスがあるかどうか、`sys._getframe` で取得できる呼び出し元のバイトコードの合計をチェックする、などです。

## インラインプラグインを使った動的ライブラリのチェック

PyArmorはクロスプロテクションを提供しますが、起動時にダイナミックライブラリをチェックし、他者によって変更されていないことを確認することも可能です。この例では、main.pyに以下のコメントを挿入することで、インラインプラグインを使用して動的ライブラリの変更時刻をチェックします。

``` python
# PyArmor Plugin: import os
# PyArmor Plugin: libname = os.path.join( os.path.dirname( __file__ ), '_pytransform.so' )
# PyArmor Plugin: if not os.stat( libname ).st_mtime_ns == 102839284238:
# PyArmor Plugin:     raise RuntimeError('Invalid Library')
```

そして、この方法でスクリプトを難読化し、インラインプラグインを有効にしてください。

``` python
pyarmor obfuscate --plugin on main.py
```

難読化されたスクリプトが起動すると、最初に以下のプラグインコードが実行されます。

``` python
import os
libname = os.path.join( os.path.dirname( __file__ ), '_pytransform.so' )
if not os.stat( libname ).st_mtime_ns == 102839284238:
    raise RuntimeError('Invalid Library')
```

## インポートされた関数が難読化されているかどうかを確認する

pytransformには1つのデコレータ `assert_armored()` と1つの関数 `check_armored()`があり、他のモジュールからのインポート関数が難読化されているかどうかを確認するために使用されます。

例えば、`main.py` と `foo.py` の2つのスクリプトがあるとします。

``` python
#
# This is main.py
#

import foo

def start_server():
    foo.connect('root', 'root password')
    foo.connect2('user', 'user password')

#
# This is foo.py
#

def connect(username, password):
    mysql.dbconnect(username, password)

def connect2(username, password):
    db2.dbconnect(username, password)
```

`main.py` の中で、`foo.connect` が難読化されていることを確認する必要があります。さもないと、エンドユーザーは難読化された `foo.py` をこのプレーンなスクリプトに置き換えて、難読化された `main.py` を実行する可能性があります。

``` python
def connect(username, password):
    print('password is %s', password)
```

パスワードが盗まれてしまうので、これを避けるために、デコレータ関数を使用して、プラグインによって接続関数が難読化されるようにします。

それでは、`main.py` を編集して、インラインプラグインのコードを挿入してみましょう。

``` python
import foo

# PyArmor Plugin: from pytransform import assert_armored

# PyArmor Plugin: @assert_armored(foo.connect, foo.connect2)
def start_server():
    foo.connect('root', 'root password')
```

そして、 `plugin on` で難読化します。

``` python
pyarmor obfuscate --plugin on main.py
```

難読化されたスクリプトは次のようになります。

``` python
import foo

from pytransform import assert_armored

@assert_armored(foo.connect, foo.connect2)
def start_server():
    foo.connect('root', 'root password')
```

`start_server` をコールする前に、デコレータ関数 `assert_armored` が両方の接続関数が `pyarmored` であることをチェックし、そうでない場合は例外を発生させます。

`check_armored()` によってもチェックできます。

``` python
import foo

from pytransform import check_armored

def start_server():

    if not check_armored(foo.connect, foo.connect2):
        print('Found hacker')
        return

    foo.connect('root', 'root password')
```

## Pythonスクリプトからpyarmorを呼び出す

os.execやsubprocess.Popenなどではなく、Pythonスクリプトの内部でPyArmorのメソッドを呼び出すことも可能です。たとえば、以下のようになります。

``` python
from pyarmor.pyarmor import main as call_pyarmor
call_pyarmor(['obfuscate', '--recursive', '--output', 'dist', 'foo.py'])
```

pyarmor の通常の出力をすべて抑制するには、`-silent` を付けて呼び出します。

``` python
from pyarmor.pyarmor import main as call_pyarmor
call_pyarmor(['--silent', 'obfuscate', '--recursive', '--output', 'dist', 'foo.py'])
```

v5.7.3以降、この方法でpyarmorを呼び出した場合、何か問題があると、sys.exitを呼び出す以外に例外が発生するようになりました。

## Web apiでライセンスキーを生成する
Pythonスクリプト内でファイルに書き出す以外に、文字列としてライセンスキーを生成することも可能です。Web apiで新しいライセンスを生成する必要がある場合に便利です。

``` python
from pyarmor.pyarmor import licenses as generate_license_key
lickey = generate_license_key(name='reg-001',
                              expired='2020-05-30',
                              bind_disk='013040BP2N80S13FJNT5',
                              bind_mac='70:f1:a1:23:f0:94',
                              bind_ipv4='192.168.121.110',
                              bind_data='any string')
print('Generate key: %s' % lickey)
```

1つのWeb APIからライセンスを生成する必要がある製品が複数ある場合、登録された製品ごとにキーワード `home` を設定します。例えば、

``` python
from pyarmor.pyarmor import licenses as generate_license_key
lickey = generate_license_key(name='product-001',
                              expired='2020-06-15',
                              home='~/.pyarmor-1')
print('Generate key for product 1: %s' % lickey)

lickey = generate_license_key(name='product-002',
                              expired='2020-05-30',
                              home='~/.pyarmor-2')
print('Generate key for product 2: %s' % lickey)
```

## 難読化スクリプトの実行時に定期的にライセンスをチェックする

通常、難読化されたスクリプトの起動時のみライセンスがチェックされます。v5.9.3以降では、1時間ごとにライセンスをチェックすることもできるようになりました。この場合、--enable-period-modeを指定して新しいライセンスを生成し、デフォルトのライセンスを上書きしてください。例えば、

``` python
pyarmor obfuscate foo.py
pyarmor licenses --enable-period-mode code-001
cp licenses/code-001/license.lic ./dist
```

## Nuitkaを使った作業

難読化されたスクリプトは、ランタイムパッケージ pytransform を追加すれば通常のスクリプトとして扱えるので、Nuitka で C プログラムに翻訳することも可能です。難読化する際には、`--restrict 0` と `--no-cross-protection` オプションを設定する必要があり、そうしないと最終的なC言語プログラムは動作しません。例えば、まずスクリプトを難読化する。

``` python
pyarmor obfuscate --restrict 0 --no-cross-protection --package-runtime 0 foo.py
```

そして、難読化したものをNuitkaで通常のPythonスクリプトとして翻訳します。

``` python
cd ./dist
python -m nuitka --include-package=pytransform foo.py
./foo.bin
```

難読化されたスクリプトのインポートモジュール（パッケージ）がNuitkaで見えないという問題があります。この問題を解決するには、まずオリジナルのスクリプトで対応する.pyiを生成し、それを難読化されたスクリプトの中にコピーしてください。例えば、

``` python
# Generating "mymodule.pyi"
python -m nuitka --module mymodule.py

pyarmor obfuscate --restrict 0 --no-bootstrap --package-runtime 0 mymodule.py
cp mymodule.pyi dist/

cd dist/
python -m nuitka --module mymodule.py
```

しかし、この方法では、バイトコードのほとんどがcコードに変換されないので、Nuitkaの機能を生かせないかもしれません。

:::note info
Nuitkaで生成したC言語プログラムをlibpythonとリンクして実行する限り、pyarmorはNuitkaと連携して動作することができます。しかし、将来的には、Nuitkaの公式サイトで言われているように、

``` python
これは、- 可能な限り - libpythonにアクセスすることなく , C言語でネイティブのデータ型を使って行われます。
```
この場合、pyarmorはNuitkaと相性が悪いかもしれません。
:::

## Cythonを使った作業
ここでは、pyarmorによって難読化されたpythonスクリプト `foo.py` をPython37でCython化する例を示します。

``` python
print('Hello Cython')
```

まず、いくつかの追加オプションで難読化します。

``` python
pyarmor obfuscate --package-runtime 0 --no-cross-protection --restrict 0 foo.py
```

難読化されたスクリプトとランタイムファイルは `dist` というパスに保存されます。各オプションの意味については、obfuscateコマンドを参照してください。

次に `foo.py` と `pytransform.py` を `-k` と `--lenient` オプションを付けて `cythonize`  して、 `foo.c` と `pytransform.c` を作成します。


``` python
cd dist
cythonize -3 -k --lenient foo.py pytransform.py
```

`k` と `--lenient` オプションがない場合、例外が発生します。

``` python
undeclared name not builtin: __pyarmor__
```

そして、`foo.c` と `pytransform.c` を拡張モジュールにコンパイルしてください。MacOSでは、以下のコマンドを実行するだけですが、Linuxでは、`cflag -fPIC` を追加してください。

``` python
gcc -shared $(python-config --cflags) $(python-config --ldflags) \
     -o foo$(python-config --extension-suffix) foo.c

gcc -shared $(python-config --cflags) $(python-config --ldflags) \
    -o pytransform$(python-config --extension-suffix) pytransform.c
```

最後にテストして、すべての `.py` ファイルを削除し、拡張モジュールをインポートしてください。

``` python
mv foo.py pytransform.py /tmp
python -c 'import foo'
```

期待通りにHello Cythonと表示されます。

## PyUpdaterとの連携

PyArmorは、この方法で [PyUpdater](https://www.pyupdater.org/) と連携する必要があります。例えば、`foo.py` というスクリプトがあるとします。

1. PyUpdaterで `foo.spec` を生成します
2. pyarmorで `foo-patched.spec` を生成します(`--debug`オプション付き)

``` python
pyarmor pack --debug -s foo.spec foo.py

# If the final executable raises protection error, try to disable restirct mode
# by the following extra options
pyarmor pack --debug -s foo.spec -x " --restrict 0 --no-cross-protection" foo.py
```

このパッチされた `foo-patched.spec` は、PyUpdaterのビルドコマンドで使用されるかもしれません。

もしPythonスクリプトが変更された場合は、再度難読化すればよいです。[obfuscate](https://pyarmor.readthedocs.io/en/latest/man.html#obfuscate)コマンドのすべてのオプションは、[pack](https://pyarmor.readthedocs.io/en/latest/man.html#pack)コマンドの出力から取得できます。

もし、上記で問題がある場合は、 PyArmorで普通にコンパイルして、zipで圧縮して、"/pyu-data/new "に入れればうまくいきます。あとは、普通に署名、処理、アップロードをすればよいです。

詳細は、コマンド [pack](https://pyarmor.readthedocs.io/en/latest/man.html#pack) の説明と、高度な使用法[難読化されたスクリプトをカスタマイズされたspecファイルでバンドルする](#難読化されたスクリプトをカスタマイズされたspecファイルでバンドルする)を参照してください。

## 難読化スクリプトのPythonインタプリタへのバインド

難読化されたスクリプトの安全性を高めるために、難読化されたスクリプトを1つの固定されたPythonインタープリタにバインドすることも可能で、Pythonの動的ライブラリが変更されると難読化されたスクリプトは動作しなくなります。

``` python
pyarmor licenses --fixed 1 -O dist/license.lic
```

難読化されたスクリプトをターゲット・マシンで起動すると、Pythonのダイナミック・ライブラリがチェックされます。このライブラリがビルドマシンにあるPythonのダイナミックライブラリと異なる場合、難読化されたスクリプトは終了します。

プロジェクトを使って難読化する場合は、まず固定ライセンスを生成してください。

``` python
cd /path/to/project
pyarmor licenses --fixed 1
```

デフォルトではlicenses/pyarmor/license.licに保存されますので、このライセンスでプロジェクトを構成します。

``` python
pyarmor config --license=licenses/pyarmor/license.lic
```

異なるプラットフォーム用のスクリプトを難読化する場合、まず対象となるプラットフォームでバインドキーを取得します。スクリプトを作成し、バインド先のPythonインタープリターで実行します。

``` python
from ctypes import CFUNCTYPE, cdll, pythonapi, string_at, c_void_p, c_char_p
from sys import platform


def get_bind_key():

    if platform.startswith('win'):
        from ctypes import windll
        dlsym = windll.kernel32.GetProcAddressA
    else:
        prototype = CFUNCTYPE(c_void_p, c_void_p, c_char_p)
        dlsym = prototype(('dlsym', cdll.LoadLibrary(None)))

    refunc1 = dlsym(pythonapi._handle, b'PyEval_EvalCode')
    refunc2 = dlsym(pythonapi._handle, b'PyEval_GetFrame')

    size = refunc2 - refunc1
    code = string_at(refunc1, size)

    print('Get bind key: %s' % sum(bytearray(code)))


if __name__ == '__main__':
    get_bind_key()
```

バインドキー xxxxxx を出力し、このバインドキーで1つの固定ライセンスを生成します。


``` python
pyarmor licenses --fixed xxxxxx -O dist/license.lic
```

また、 "," で区切られた複数のキーを渡すことで、多くのPythonインタプリタにライセンスをバインドすることも可能です。

``` python
pyarmor licenses --fixed 1,key2,key3 -O dist/license.lic
pyarmor licenses --fixed key1,key2,key3 -O dist/license.lic
```

スペシャル `Key1`は、現在のPythonインタプリタを意味します。

:::note info
32bit Windowsでは、マシンによってバインドキーが異なるため、同じマシンでpythonを再起動してもバインドキーが変わってしまう可能性があるので、この機能は使用しないでください。
:::

## クロスプロテクションコードのカスタマイズ

PyArmorのコアダイナミックライブラリを保護するために、デフォルトの保護コードはエントリスクリプトに注入されます。[エントリースクリプトの特殊な取り扱いについて](#エントリースクリプトの特殊な取り扱いについて)を参照してください。しかし、この公開された保護コードは意図的に回避される可能性があるため、より良い方法は、独自の保護コードを書くことです。

v6.2.0以降、[runtime](https://pyarmor.readthedocs.io/en/latest/man.html#runtime)コマンドはデフォルトの保護コードを生成することができるようになり、これは独自の保護コードを書くためのテンプレートとなります。もちろん、あなた自身で書いても構いません。ただし、難読化されたスクリプトを実行する際に、ランタイムファイルが他の人に変更されないことを確認することができれば、です。

まず、保護スクリプト `build/pytransform_protection.py` を生成します。

``` python
pyarmor runtime --advanced 2 --output build
```

そのスクリプトを編集し、難読化した後、このカスタマイズしたスクリプトにオプション `--cross-protection` を設定してください。例えば、

``` python
pyarmor obfuscate --cross-protection build/pytransform_protection.py \
              --advanced 2 foo.py
```

:::note info
コマンド obfuscate のオプション `--advanced` は、コマンド runtime のオプションと同じにする必要があります。
:::

## ランタイムファイルlicense.licの任意の場所への保存

ランタイムパッケージにシンボルリンクを作成することで、難読化スクリプトを実行する際に、ランタイムファイルlicense.licを任意の場所に保存することが簡単にできます。

例えば、Linuxの場合、ライセンスファイルを `/opt/my_app` に格納します。

``` python
ln -s /opt/my_app/license.lic /path/to/obfuscated/pytransform/license.lic
```

Windowsの場合、ライセンスファイルは `C:/Users/Jondy/my_app` に保存されます。

``` python
mklink \path\to\obfuscated\pytransform\license.lic C:\Users\Jondy\my_app\license.lic
```
難読化したパッケージを配布する場合は、インストール後にこの関数を実行すればよいです。

``` python
import os

def make_link_to_license_file(package_path, target_license="/opt/mypkg/license.lic"):
    license_file = os.path.join(package_path, 'pytransform', 'license.lic')
    if os.path.exists(license_file):
        os.rename(license_file, target_license)
    os.symlink(target_license, license_file)
```

## 同じマシンで複数のpyarmorを登録する

v5.9.0から、pyarmorは環境変数 `PYARMOR_HOME` からライセンスとカプセルのデータを読み取りますが、デフォルト値は `~/.pyarmor` です。そのため、pyarmorを実行する前に環境変数 `PYARMOR_HOME` を別のパスに設定すれば、1つのマシンで複数のpyarmorを登録することが簡単にできます。

また、以下の方法で2つ目のプロジェクト用に新しいコマンドpyarmor2を作成することができます。

Linux では、シェルスクリプト `pyarmor2` を作成します。

``` python
export PYARMOR_HOME=$HOME/.pyarmor_2
pyarmor "$@"
```

`usr/local/pyarmor2` に保存し、そのモードを変更します。

``` python
chmod +x /usr/local/pyarmor2
```

Windowsの場合、batスクリプト `pyarmor2.bat` を作成します。

``` python
SET PYARMOR_HOME=%HOME%\another_pyarmor
pyarmor %*
```

その後、2つ目のプロジェクトでpyarmor2を実行します。

``` python
pyarmor2 register pyarmor-regkey-2.zip
pyarmor2 obfuscate foo2.py
```

## 難読化された1つのパッケージのライセンス情報を取得する方法

難読化されたパッケージのライセンス情報を取得する方法は？ v6.2.5以降では、ランタイムパッケージpytransformのパスで次のスクリプトを実行するだけです。

``` python
from pytransform import pyarmor_init, get_license_info
pyarmor_init(is_runtime=1)
licinfo = get_license_info()
print('This obfuscated package is issued by %s' % licinfo['ISSUER'])
print('License information:')
print(licinfo)
```

スーパーモードによって難読化されたスクリプトには、pytransformパッケージはなく、拡張機能であるpytransformがあります。これは似ていて、よりシンプルです

``` python
from pytransform import get_license_info
licinfo = get_license_info()
print('This obfuscated package is issued by %s' % licinfo['ISSUER'])
print('License information:')
print(licinfo)
```

v6.2.7以降では、この方法でヘルパースクリプトを呼び出すこともできるようになりました。


``` python
cd /path/to/obfuscated_package
python -m pyarmor.helper.get_license_info
```

## データファイルの保護方法

これはまだ実験的な機能です。

PyArmorはデータファイルに触れませんが、データファイルをPythonモジュールにラップし、そのデータモジュールを制限モード4で難読化し、難読化したスクリプトからしかインポートできないようにすることは可能です。この方法で、データファイルをPyArmorで保護することができます。

v6.2.7以降では、データファイルからPythonモジュールを作成するヘルパースクリプトが用意されています。

``` python
python -m pyarmor.helper.build_data_module data.txt > data.py
```

次に、このデータモジュールを制限モード4で難読化する。

``` python
pyarmor obfuscate --exact --restrict 4 --no-runtime data.py
```

その後、そのデータファイルを他の難読化されたスクリプトで使用します。例えば、

``` python
import data

# Here load the content of data file to memory variable "text"
# And clear it from memory as exiting the context
with data.Safestr() as text:
  ...
```

v6.2.7以前は、このヘルパースクリプト [build_data_module.py](https://raw.githubusercontent.com/dashingsoft/pyarmor/master/src/helper/build_data_module.py) をダウンロードし、直接実行してください。

``` python
python build_data_module.py data.txt > data.py
```

## docstringの削除方法

コマンドラインで `PYTHONOPTIMIZE=2` を設定することで、難読化されたスクリプトからdocstringを削除することができます。たとえば、

``` python
# In linux
PYTHONOPTIMIZE=2 pyarmor obfuscate foo.py

# In Windows
set PYTHONOPTIMIZE=2
pyarmor obfuscate foo.py
```

## スレッドとマルチプロセッシングの制限モードの使用

`restrict` モード 3 と 4 でマルチプロセッシングやスレッドを直接使用すると、 保護例外を訴えることがあります。なぜなら、これらのシステムモジュールは難読化されていませんが、 `restrict` モジュールの中の関数を呼び出そうとするからです。

解決策の一つは、Thread を拡張して、その `run` メソッドをラムダ関数で上書きすることです。たとえば、

``` python
from threading import Thread

class PrivateThread(Thread):

    def lambda_run(self):
        try:
            if self._target:
                self._target(*self._args, **self._kwargs)
        finally:
            del self._target, self._args, self._kwargs

    run = lambda self : self.lambda_run()

def foo():
    print('Hello')

t = PrivateThread(target=foo)
t.start()
```

システムスレッドを拡張し、自分で `run` メソッドを定義している場合は、 `run` を `lambda_run` にリネームし、 `run` ラムダメソッド を追加するだけです。たとえば、

``` python
from threading import Thread

class MyThread(Thread):

    # def run(self):
    def lambda_run(self):
        ...

    # Define a lambda method `run`
    run = lambda self : self.lambda_run()
```

もう一つの解決策は、制限モード 1 のパブリックモジュールを定義し、プレーンなスクリプトにこのパブリックモジュール内の関数を呼び出させることです。

たとえば、次のスクリプトは、パブリックモジュール `pub_foo.py` を使用した `foo.py` です。

``` python
import multiprocessing as mp
import pub_foo

def hello(q):
    print('module name: %s' % __name__)
    q.put('hello')

if __name__ == '__main__':
    ctx = mp.get_context('spawn')
    q = ctx.Queue()
    # call "proxy_hello" instead private "hello"
    p = ctx.Process(target=pub_foo.proxy_hello, args=(q,))
    p.start()
    print(q.get())
    p.join()
```

publicモジュール `pub_foo.py` の内容

``` python
import foo

def proxy_hello(q):
    return foo.hello(q)
```

`foo.py` をモード 3 で、 `pub_foo.py` をモード 1 で難読化します。

``` python
pyarmor obfuscate --restrict 3 foo.py

# both of options --exact and --no-runtime are required
pyarmor obfuscate --restrict 1 --exact --no-runtime pub_foo.py
```

3つ目の解決策は、システムモジュールのスレッド化、またはパッケージのマルチプロセシングの一部のモジュールをモード1で難読化することです。呼び出し元が難読化されていることを確認してください。

## 難読化されたスクリプトでPyInstallerバンドルを再パックする

v6.5.5 以降、PyArmor は難読化されたスクリプトで PyInstaller バンドルを再パックするために使用するヘルパースクリプト `repack.py` を提供しています。

まず PyInstaller でスクリプトをパックし、次に PyArmor でスクリプトを難読化し、最後にこのスクリプトを実行して難読化されたスクリプトでバンドルを再パックします。

+ PyInstallerでスクリプトをパックし、最終的なバンドルが動作することを確認します。実際のスクリプトでは、他のオプションが必要な場合がありますので、[PyInstallerドキュメント](https://pyinstaller.org/en/stable/)を参照してください。このステップで最終的なバンドルが動作しない場合は、[PyInstaller issues](https://github.com/pyinstaller/pyinstaller/issues) に問題を報告してください。

``` python
# One folder mode
pyinstaller foo.py

# Check it works
dist/foo/foo.exe

# If prefer to one file mode, run this command
pyinstaller --onefile foo.py

# Check it works
dist/foo.exe
```
スクリプトを `obfdist` で難読化し、難読化されたスクリプトが動作することを確認します。実際のスクリプトでは、他のオプションが必要な場合があります。 [obfuscate](https://pyarmor.readthedocs.io/en/latest/man.html#obfuscate) を調べると、より多くの使い方が見つかりますし、[build](https://pyarmor.readthedocs.io/en/latest/man.html#build)によってスクリプトが難読化されることもあります。

``` python
# Option --package-runtime should be set to 0
pyarmor obfuscate -O obfdist --package-runtime 0 foo.py

# If prefer to super mode, run this command
pyarmor obfuscate -O obfdist --advanced 2 foo.py

# Check it works
python dist/foo.py
```

最終の実行ファイルを再パックし、PyInstallerと同じPythonインタプリタを使用します。

``` python
# If one folder mode
python repack.py -p obfdist dist/foo/foo.exe

# Overwrite the old one
cp foo-obf.exe dist/foo/foo.exe

# If one file mode
python repack.py -p obfdist dist/foo.exe

# Overwrite the old one
cp foo-obf.exe dist/foo.exe
```

ここで、 `foo-obf.exe` はパッチを当てたバンドルです。

obfdist に含まれる難読化されたスクリプトは、PyInstaller バンドル内のスクリプトと同じパスにあることが必要です。オプション `-d` は、アーカイブの情報を表示し、アーカイブの構造に従って難読化されたスクリプトを適切な場所にコピーするために使用されます。例えば、このコマンドはアーカイブの情報を表示します。

``` python
python repack.py -d -p obfdist dist/foo.exe
```

難読化されたスクリプトの構造が変更された場合、Pythonで直接メインスクリプトを実行し、まだ動作することを確認ください。

:::note info
v6.5.5以前は、以下のサイトからrepack.pyをダウンロードしてください。

[https://github.com/dashingsoft/pyarmor/raw/master/src/helper/repack.py](https://github.com/dashingsoft/pyarmor/raw/master/src/helper/repack.py)

v6.5.5以降では、以下の方法で実行してください。

``` python
python -m pyarmor.helper.repack -p obfdist dist/foo
```
:::

## 難読化されたスクリプトを拡張モジュールにビルドする

難読化されたスクリプトを拡張機能にビルドするためのヘルパースクリプト `buildext.py` が `pyarmor` のパッケージに含まれています。

1. スクリプトを難読化するには、例えば `--no-cross-protection` と `--restrict 0` を指定します。

``` python
pyarmor obfuscate --no-cross-protection --restrict 0 foo.py
```

難読化されたスクリプトを拡張機能にビルドします。例えば、

``` python
python buildext.py dist/foo.py
```

オプション `-i` を指定すると、難読化されたスクリプトはビルド後に削除されるので、出力パスの `dist` はクリーンな状態になります。例えば、

``` python
python buildext.py -i dist/
```

デフォルトではdist内の難読化されたスクリプトのみが扱われますが、サブディレクトリがある場合は、このように全てのスクリプトをリストアップします。

``` python
python buildext.py dist/ dist/a/ dist/b/
```


あるいは、コマンドラインにあるすべてのスクリプトをリストアップします。例えば、

``` python
# In Linix
python buildext.py $(find dist/ -name "*.py")

# In Windows
FOR /R dist\ %I IN (*.py) DO python buildext.py %I
```

`__name__ == "__main__"` の場合、拡張機能はこのブロックを無視します。このブロックをメインスクリプトとして実行するには、例えば -e オプションでビルドして実行ファイルを生成してください。

``` python
python buildext.py -e dist/foo.py
dist/foo.exe
```

この実行ファイルは、現在のPython環境で実行する必要があります。それは以下と等しいです。

``` python
python dist/foo.py
```

`-h` でより多くの使用法とオプションを表示します。

``` python
python buildext.py -h
```

:::note info
v6.6.0以前は、buildext.pyを以下からダウンロードしてください。

[https://github.com/dashingsoft/pyarmor/raw/master/src/helper/buildext.py](https://github.com/dashingsoft/pyarmor/raw/master/src/helper/buildext.py)

v6.6.0以降では、以下の方法で実行してください。

``` python
python -m pyarmor.helper.buildext ...
```
:::

::: note info
Windowsの場合、拡張機能のビルドに問題がある場合は、demo.cをdemo.pydにビルドするための簡単なsetup.pyを書いてください。

``` python
    from distutils.core import setup, Extension

    module1 = Extension('demo',
                        sources = ['demo.c'])

    setup (name = 'pyarmor.helper.buildext',
           version = '1.0',
           description = 'This is a helper package to build extension',
           ext_modules = [module1])

Then run it::

    python setup.py build_ext
```
:::

## 難読化されたパッケージをpipで配布する

ここにシンプルなパッケージがあります。

``` python
.
└── mylib
    ├── mylib
    │   ├── __init__.py
    │   └── main.py
    └── setup.py
```

まず、 `--enable-suffix 1` でユニークな[ランタイムパッケージ](#ランタイムパッケージ)を生成します。

``` python
cd mylib
pyarmor runtime -O dist/share --enable-suffix 1
```

そして、このランタイムを用いてパッケージを難読化します。

``` python
pyarmor obfuscate --with-runtime @dist/share mylib/__init__.py
```

次に `setup.py` を編集し、必要なランタイムファイルを全てデータファイルとして追加します。例えば、ユニークなパッケージ名が `pytransform_vax_xxxxxx` であるとします。

``` python
setup(name='mylib',
      ...
      packages=['mylib'],
      package_dir={'mylib': 'dist'},
      data_files=[('pytransform_vax_xxxxxx', 'dist/share/pytransform_vax_xxxxxx/*')]
      ...
      )
```

最後にソースパッケージをビルドします。

``` python
python setup.py sdist
```

::: note info
`setup.py` を難読化しないこと。

スーパーモードでは、ランタイムファイルが異なるので、必要に応じて `setup.py`を修正してください。
:::

## 異なるPythonバージョンによる難読化スクリプトの実行

この機能はv6.8.0から導入されました。

一般的に、難読化されたスクリプトは、あるPythonのバージョンでしか実行できません。他のバージョンのPythonで実行するには、まず異なるバージョンのPythonでスクリプトを難読化し、それらを1つのスクリプトにマージする方法があります。

pyarmorのパッケージにはmerge.pyというヘルパースクリプトがあり、難読化された異なるスクリプトを一つにマージするのに使用されます。

ここでは、その基本的な使い方を説明します。

``` python
# First obfuscate the scripts by Python 2.7
python2.7 pyarmor.py obfuscate -O py27 foo.py

# Then obfuscate the scripts by Python 3.8
python3.8 pyarmor.py obfuscate -O py38 foo.py

# Finally run this script to merge all of them
python merge.py py38/ py27/

# Look the results
ls merged_dist/
```

また、スーパーモードにも対応しています。

``` python
# First obfuscate the scripts by Python 2.7
python2.7 pyarmor.py obfuscate --advanced 2 -O py27 foo.py

# Then obfuscate the scripts by Python 3.8
python3.8 pyarmor.py obfuscate --advanced 2 -O py38 foo.py

# Finally run this script to merge all of them
python merge.py py38/ py27/

# Look the results
ls merged_dist/
```

:::note info
マージされたスクリプトがプロテクションエラーを起こす場合、オプション `--no-cross-protection` を使ってスクリプトを難読化するようにしてください。
:::

:::note info
v6.8.0以前は、merge.pyを以下の場所からダウンロードしてください。

[https://github.com/dashingsoft/pyarmor/raw/master/src/helper/merge.py](https://github.com/dashingsoft/pyarmor/raw/master/src/helper/merge.py)

v6.8.0以降では、以下の方法で実行してください。

``` python
python -m pyarmor.helper.merge ...
```
:::

# 5.Pyarmored Wheelをビルドする

最近の Python パッケージは [pyproject.toml](https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml/) ファイルを含むことができます。これは PEP 518 で初めて導入され、その後 PEP 517、PEP 621、PEP 660 で拡張されました。このファイルにはビルドシステムの要件と情報が含まれており、pip がパッケージをビルドするために使用されます。

v7.2.0以降、pyarmorはPEP 517のバックエンドとして、`setuptools.build_meta` に基づいてpyarmored Wheelを構築することができるようになりました。

パッケージ構造の例を示します。

``` python
mypkg/
    setup.py
    pyproject.toml
    src/
        __init__.py
        ...
```

`pyproject.toml` はこのような感じです。

``` python
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
```

まず、バックエンドの `setuptools.build_meta` が動作することを、以下のコマンドを実行して確認し、wheel をビルドしてください。動作しない場合は、関連するノウハウの [pip wheel](https://pip.pypa.io/en/stable/cli/pip_wheel/)を学習し、動作するようにしてください。

``` python
cd mypkg/
pip wheel .
```

`pyproject.toml` を編集し、ビルドバックエンドを `pyarmor.build_meta` に変更します。

``` python
[build-system]
requires = ["setuptools", "wheel", "pyarmor>7.2.0"]
build-backend = "pyarmor.build_meta"
```

同じコマンドでpyarmored wheelを作ります。

``` python
cd mypkg/
pip wheel .
```

また、環境変数 `PIP_PYARMOR_OPTIONS` に難読化オプションを追加設定することで、 [スーパーモード](#super-mode) で `Pyarmored Wheel` を構築することも可能です。

``` python
cd mypkg/

# In Windows
set PIP_PYARMOR_OPTIONS=--advanced 2
pip wheel .

# In Linux or MacOs
PIP_PYARMOR_OPTIONS="--advanced 2" pip wheel .
```

v7.2.4以降では、`pip configuration pyarmor.advanced` を使用して、 [スーパーモード](#super-mode)付き `pyarmored wheel` を構築することができます。

最初に [pip config](https://pip.pypa.io/en/stable/cli/pip_config/) を実行します。

``` python
pip config set pyarmor.advanced 2
```

次に、ビルドコマンドを実行します。

``` python
cd mypkg/
pip wheel .
```

この方法で、この設定を削除する。

``` python
pip config unset pyarmor.advanced
```

## どのように機能するか

pyarmorによって難読化されたPythonスクリプトは、通常のPythonスクリプトと同じで、ダイナミックライブラリや拡張機能が追加されています。つまり、`pyarmor.build_meta` は単に、

1. `setuptools.build_meta`を呼び出してwheelをビルドします
2. wheel を解凍します
3. 解凍したパスのすべての `.py` ファイルを難読化します
4. ホイールファイルRECORDにpyarmorランタイムファイルを追加します
5. パッチを適用したwheelを再パックする

詳細は [pyarmor/build_meta.py](https://github.com/dashingsoft/pyarmor/blob/master/src/build_meta.py#L86) の `bdist_wheel` という関数を参照してください。

簡単なスクリプトで、基本的な機能を実装しているだけなので、何か問題があれば、手動で行うか、以下の手順でシェルスクリプトを記述してください。

1. オリジナルパッケージでwheelをビルド
2. このwheelを以下のコマンドで一時的なパスに解凍します。
   `python3 -m wheel unpack -dest /path/to/temp xxx.whl`
3. `pyarmor obfuscate` でスクリプトを難読化し、unpackパスのすべての `.py` ファイルを難読化されたもので上書きします。
4. wheelファイル RECORD にpyarmorランタイムファイルを追加し、アンパックパスで検索します。
5. パッチを適用したwheelを再パックする::
   `python3 -m wheel pack /path/to/temp`

この機能に関して、さらなる要求があれば、プルリクエストを歓迎します。

Pyarmored Wheelのビルドはヘルパー機能であり、これ以上のサポートはありません。

:::note info
バイナリファイルを含むパッケージからwheelをビルドする方法を知らない場合は、難読化されたスクリプトを通常のスクリプトとして使用する方法を参照して、独学で勉強してください。
:::

# 6.事例紹介

以下にいくつかの例を紹介します。

## PyQtアプリケーションの難読化およびパッケージ化

PyQtをベースにしたeasy-hanというツールがあります。主なファイルを以下に示します。

``` python
config.json

main.py
ui_main.py
readers/
    __init__.py
    msexcel.py

tests/
vnev/py36
```

このツールをPyArmorでパックするために使用されるシェルスクリプトはこちらです。

``` python
cd /path/to/src
pyarmor pack --name easy-han \
             -e " --hidden-import comtypes --add-data 'config.json;.'" \
             -x " --exclude vnev --exclude tests" main.py

cd dist/easy-han
./easy-han
```

オプション `-e` で [PyInstaller](https://pyinstaller.org/en/stable/) を実行するための追加オプションを渡し、これらのオプションが [PyInstaller](https://pyinstaller.org/en/stable/) で動作することを確認します。

``` python
cd /path/to/src
pyinstaller --name easy-han --hidden-import comtypes --add-data 'config.json;.' main.py

cd dist/easy-han
./easy-han
```

オプション `-x` でスクリプトを難読化するための追加オプションを渡すと、tests と vnev のパスに多くの `.py` ファイルがありますが、それらすべてを難読化する必要はありません。オプション `--exclude` を渡すと、これらのファイルを除外することができ、これらのオプションが `obfuscate` コマンドで動作することを確認できます。

``` python
cd /path/to/src
pyarmor obfuscate -r --exclude vnev --exclude tests main.py
```

:::note warn
[pack](https://pyarmor.readthedocs.io/en/latest/man.html#pack) コマンドは自動的にスクリプトを難読化しますので、難読化されたスクリプトをパックすることはしないでください。
:::

:::note info
オプション `--advanced 2` を追加で渡すことで、[スーパーモード](#super-mode) を有効にし、セキュリティを向上させることができます。たとえば、

``` python
pyarmor pack -x " --advanced 2 --exclude tests" foo.py
```
:::

## 難読化された Django サイトを Apache と mod_wsgi で実行する

Djangoのシンプルなサイトがあります。

``` python
/path/to/mysite/
    db.sqlite3
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    polls/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        urls.py
        views.py
```

最初にすべてのスクリプトを難読化します。

``` python
# Create target path
mkdir -p /var/www/obf_site

# Copy all files to target path, because pyarmor don't deal with any data files
cp -a /path/to/mysite/* /var/www/obf_site/

cd /path/to/mysite

# Obfuscating all the scripts in the current path recursively, specify the entry script "wsgi.py"
# The obfuscate scripts will be save to "/var/www/obf_site"
pyarmor obfuscate --src="." -r --output=/var/www/obf_site mysite/wsgi.py
```

次に、Apacheのサーバー設定ファイルを編集します。

``` python
WSGIScriptAlias / /var/www/obf_site/mysite/wsgi.py
WSGIPythonHome /path/to/venv

# The runtime files required by pyarmor are generated in this path
WSGIPythonPath /var/www/obf_site

<Directory /var/www/obf_site/mysite>
    <Files wsgi.py>
        Require all granted
    </Files>
</Directory>
```

最後にApacheを再起動します。

``` python
apachectl restart
```

# 7.Projectの活用

Projectとは、難読化されたスクリプトを管理するための、独自の設定ファイルを含むフォルダです。

難読化されたスクリプトをProjectで管理することには、いくつかの利点があります。

+ インクリメントビルド、前回のビルドから更新されたスクリプトのみ難読化されます。
+ プロジェクト内の難読化されたスクリプトをフィルタリングし、一部のスクリプトを除外することができます。
+ 異なるモードでスクリプトを難読化することができます。
+ 難読化されたスクリプトをより簡単に管理できます。

## Projectによる難読化スクリプトの管理

プロジェクトを作成するには、[init](https://pyarmor.readthedocs.io/en/latest/man.html#init) コマンドを使用します。

``` python
cd examples/pybench
pyarmor init --entry=pybench.py
```

プロジェクトの設定ファイル `.pyarmor_config` が現在のパスに作成されます。または、別のパスにプロジェクトを作成します。

``` python
pyarmor init --src=examples/pybench --entry=pybench.py projects/pybench
```

プロジェクトパス `projects/pybench` が作成され、 `.pyarmor_config` はそこに保存されます。

プロジェクトの一般的な使い方は、プロジェクトパスの中であらゆることを行うことです。

``` python
cd projects/pybench
```

プロジェクト情報を表示します。

``` python
pyarmor info
```

このプロジェクトに含まれるすべてのスクリプトを `build` コマンドで難読化する。

``` python
pyarmor build
```

[config](https://pyarmor.readthedocs.io/en/latest/man.html#config) コマンドでプロジェクトの設定を変更します。

例えば、 `dist` , `test` を除外すると、これらのフォルダにある `.py` ファイルは難読化されません。

``` python
pyarmor config --manifest "include *.py, prune dist, prune test"
```

データファイルもマニフェストに記載することができ、プロジェクトのビルド時に出力パスにコピーされます。たとえば、以下のようになります。

``` python
pyarmor config --manifest "include *.py, include config.json"
pyarmor build
```

`--manifest` を指定すると、プロジェクト・スクリプトを正確に選択することができます。
詳細は、[プロジェクト構成ファイル](#プロジェクト構成ファイル) セクションの `manifest` 属性の説明を参照してください。

強制リビルド:

``` python
pyarmor build --force
```

難読化されたスクリプトの実行：

``` python
cd dist
python pybench.py
```

いくつかのスクリプトを変更した後、再度 [ビルド](https://pyarmor.readthedocs.io/en/latest/man.html#build) を実行してください。

``` python
cd projects/pybench
pyarmor build
```

## 異なるモードによるスクリプトの難読化

最初に、 [難読化したスクリプトのモード](#13難読化したスクリプトのモード) を参照しそれぞれのモードを設定します。

``` python
pyarmor config --obf-mod=1 --obf-code=0
```

その後、新しいモードでスクリプトを難読化する。

``` python
pyarmor build -B
```

## 子プロジェクトによる特殊なスクリプトの難読化

プロジェクト内のほとんどのスクリプトは制限モード3で難読化されているが、いくつかのスクリプトは制限モード2で難読化する必要があるとします。この場合、子プロジェクトが適切です。

１. 最初に、ソースパスにプロジェクトを作成します。 

``` python
cd /path/to/src
pyarmor init --entry foo.py
pyarmor config --restrict 3
```

２. 次に、プロジェクトの設定ファイルをクローンし、 `.pyarmor_config-1` という名前の子プロジェクトを作成します。

``` python
cp .pyarmor_config .pyarmor_config-1
```

３. 次に、子プロジェクトに、entryスクリプトなし、制限モード2で、特別なスクリプトを構成します。

``` python
pyarmor config --entry "" \
               --manifest "include a.py other/path/sa*.py" \
               --restrict 2 \
               .pyarmor_config-1
```


４. 最後にプロジェクトと子プロジェクトをビルドします。

``` python
pyarmor build -B
pyarmor build --no-runtime -B .pyarmor_config-1
```

## プロジェクト構成ファイル

各プロジェクトには、`configure` ファイルがあります。 `.pyarmor_config` という名前のjsonファイルで、プロジェクトのパスに格納されています。

+ name
>プロジェクト名

+ title
>プロジェクトのタイトル

+ src
>マニフェスト テンプレート文字列でファイルを照合するためのベースパス。
>絶対パス、またはプロジェクト フォルダに基づく相対パス。

+ manifest
>Python Distutils の MANIFEST.in と同様に、難読化するファイルを文字列で指定します。
>``` python
>global-include *.py
>```
>これは、src ツリー内の任意の場所にあるすべての .py ファイルがマッチすることを意味します。
>複数のマニフェストテンプレートコマンドはカンマで区切られます。たとえば、
>``` python
>global-include *.py, exclude __mainfest__.py, prune test
>```
>データファイルもマニフェストで選択することができ、プロジェクトのビルド時に出力パスにコピーされます。
>[https://docs.python.org/2/distutils/sourcedist.html#commands](https://docs.python.org/2/distutils/sourcedist.html#commands) を参照してください。

+ is_package
>使用可能な値は、{0, 1, None}です。
>1に設定すると、難読化されたスクリプトを保存するための最終的なパスとして、srcのベースネームが出力に追加されますが、ランタイムファイルはパス出力に含まれたままです。
>プロジェクトの `init` 時に `--type` が指定されていない場合、エントリスクリプトが `__init__.py` ならば `1` 、そうでなければ `None` が設定されます。

+ restrict_mode
>使用可能な値は、{0, 1, 2, 3, 4}です。
>デフォルトでは、`1` が設定されています。
> [Restrict Mode](#restrict-mode)　を参照してください。

+ entry
>一つまたは複数のエントリースクリプトを含んだ文字列。
>プロジェクトをビルドするときに、各エントリに以下のブートストラップコードを挿入します。
>``` python
>from pytransform import pyarmor_runtime
>pyarmor_runtime()
>```
>エントリ名はsrcからの相対パス、または絶対パスでファイル名を指定します。
>複数のエントリーはカンマで区切られます。
>``` python
>main.py, another/main.py, /usr/local/myapp/main.py
>```
>manifest がこのエントリーを指定していない場合、エントリーは難読化されていない可能性があることに注意してください。

+ output
>ビルドの出力を保存するためのパス。プロジェクトのパスからの相対パスです。

+ capsule
>:::note warn
>v5.9.0から削除されました。
>:::
>プロジェクトカプセルのファイル名。絶対パスでない場合は、プロジェクト・パスからの相対パスとなります。

+ obf_code
>各コードオブジェクトのバイトコードを難読化する方法です。 [Obfuscating Code Mode](#obfuscating-code-mode) を参照してください。
>+ 0
>難読化しない
>+ 1 (デフォルト)
>デフォルトのアルゴリズムで各コードオブジェクトを難読化する
>+ 2
>より複雑なアルゴリズムで各コードオブジェクトを難読化する

+ wrap_mode
>使用可能な値は、{0, 1, None}です
>try...finalブロックでコードオブジェクトをラップするかどうかです。
>デフォルトは `1` です。　[Wrap Mode](#wrap-mode) を参照してください。

+ obf_mod
>モジュールのコードオブジェクト全体を難読化する方法です。 [Obfuscating module Mode](#obfuscating-module-mode) を参照してください。
>+ 0
>難読化しない
>+ 1 (デフォルト)
>DESアルゴリズムによるバイトコードの難読化を行う

+ cross_protection
>難読化したスクリプトの動的ライブラリを保護する方法。
>+ 0
>保護しない
>+ 1
>デフォルトのテンプレートに保護コードを挿入します。 [エントリースクリプトの特殊な取り扱いについて](#エントリースクリプトの特殊な取り扱いについて) を参照してください。
>+ ファイル名
>このファイルからデフォルトテンプレート以外のプロテクトコードのテンプレートを読み込む。

+ runtime_path
>なし、または任意のパス。
>難読化されたスクリプトを実行するとき、ダイナミックライブラリ `_pytransform` をどこで見つけるかを指定します。デフォルト値は `None` で、 [ランタイムパッケージ](#ランタイムパッケージ) 内、または `pytransform.py` と同じパスにあることを意味します。
>これは、難読化されたスクリプトをzipファイルにパックするときに便利です。例えば、難読化されたスクリプトをパッケージするために `py2exe` を使用します。`runtime_path` を空の文字列に設定し、 [ランタイムファイル](#ランタイムファイル) をzipファイルの同じパスにコピーすれば、この問題は解決します。

+ plugins
>なし、または文字列のリスト
>難読化されたスクリプトのライセンスタイプを拡張する、複数のプラグインがサポートされています。たとえば、
>``` python
>plugins: ["check_ntp_time", "show_license_info"]
>```
>プラグインの使用方法については、 [プラグインを使用したライセンスタイプの拡張](#プラグインを使用したライセンスタイプの拡張) を参照してください。

+ package_runtime
>ランタイムファイルの保存方法。
>+ 0
>obufscatedスクリプトと同じパスに保存します。
>+ 1 (デフォルト)
>パッケージとしてサブパスpytransformに保存します。

+ enable_suffix
>:::note info
>v5.8.7での新機能
>:::
>ランタイムパッケージ（モジュール）とブートストラップコードを生成する方法。
>+ 0 (デフォルト)
>ランタイムパッケージ(モジュール)の名前に接尾辞をつけない。
>+ 1
>ランタイムパッケージ(モジュール)の名前にサフィックスがある、例えば `pytransform_vax_00001`

+ platform
>:::note info
>v5.9.0での新機能
>:::
>文字列は、1つまたは複数のプラットフォームを含みます。複数のプラットフォームはカンマで区切られます。
>クロスプラットフォーム難読化を行わない場合は、Noneまたは空白にしてください。

+ license_file
>:::note info
>v5.9.0での新機能
>:::
>ライセンスファイルを、デフォルトのものとは別に使用する。
>デフォルトのものを使用する場合は、None または空白にしてください。

+ bootstrap_code
>:::note info
>v5.9.0での新機能
>:::
>難読化されたエントリースクリプトの [ブートストラップコード](#ブートストラップコード) を生成する方法。
>+ 0
>ブートストラップコードをエントリースクリプトに挿入しない
>+ 1 (デフォルト)
>ブートストラップ・コードをエントリー・スクリプトに挿入します。スクリプト名が __init__.py の場合は、先頭のドットで相対インポート、それ以外の場合は絶対インポートにします。
>+ 2
>ブートストラップ・コードは、常にエントリー・スクリプトの中で、リーディング・ドットなしの絶対インポートにされます。
>+ 3
ブートストラップ・コードは、常にエントリ・スクリプトの中で先行するドットを持つ相対的なインポートを行う。

# 8.Manページ

オリジナルページリンクのみです。
[https://pyarmor.readthedocs.io/en/latest/man.html](https://pyarmor.readthedocs.io/en/latest/man.html)

# 9.難読化されたスクリプトを理解する

## グローバルカプセル
HOMEパスの.pyarmor_capsule.zipは、Global Capsuleと呼ばれます。PyArmorは、スクリプトを難読化するとき、または難読化されたスクリプトのライセンスを生成するときに、Global Capsuleからデータを読み取ります。

PyArmorのすべての試用版は、`pyarmor obfuscate` コマンドを実行したときに暗黙的に作成される `.pyarmor_capsule.zip` を共有しています。これは1024ビットのRSA鍵を使用し、パブリックカプセルと呼ばれます。

購入版では、各ユーザは2048ビットのRSA鍵を使った専用のプライベートカプセルを1つ受け取ります。

通常、このカプセルはビルドマシンにのみ存在し、難読化されたスクリプトでは使用されず、エンドユーザにも配布されることはないはずです。

:::note warn
このカプセルは、難読化されたスクリプトをハックするのに他の人を助けるかもしれません。あなたの個人的なカプセルを他の人に教えないでください。
:::

## 難読化されたスクリプト
PyArmorによってスクリプトが難読化された後、distフォルダには難読化されたスクリプトを実行するために必要なすべてのファイルが格納されています。

``` python
dist/
    myscript.py
    mymodule.py

    pytransform/
        __init__.py
        _pytransform.so/.dll/.dylib
```

v6.3以前は、2つのextraファイルがあります。

``` python
pytransform.key
license.lic
```

難読化されたスクリプトは、通常のPythonスクリプトです。モジュールdist/mymodule.pyはこのようなものになります。

``` python
__pyarmor__(__name__, __file__, b'\x06\x0f...', 1)
```

エントリースクリプトのdist/myscript.pyはこのようなものになります。

``` python
from pytransform import pyarmor_runtime
pyarmor_runtime()
__pyarmor__(__name__, __file__, b'\x0a\x02...', 1)
```

### 超難読スクリプト
もしスクリプトが [スーパーモード](#super-mode) によって難読化されているなら、それは全く違うものです。ランタイムファイルは1つだけで、それは拡張モジュール `pytransform`です。 `dist` にはこれらのファイルしかありません。

``` python
myscript.py
mymodule.py

pytransform.so or pytransform.dll
```

難読化されたスクリプトはすべてこのようなものになります。

``` python
from pytransform import pyarmor
pyarmor(__name__, __file__, b'\x0a\x02...', 1)
```

または、拡張子名にサフィックスが含まれます。たとえば、

``` python
from pytransform_vax_000001 import pyarmor
pyarmor(__name__, __file__, b'\x0a\x02...', 1)
```

### エントリースクリプト
PyArmorでは、エントリスクリプトは、pythonインタプリタプロセスで最初に実行される、またはインポートされる難読化されたスクリプトのことです。たとえば、1つのpythonパッケージだけが難読化されている場合、__init__.pyがエントリスクリプトとなります。

## ブートストラップコード
Bootstrap Codeというエントリースクリプトの最初の2行です。エントリースクリプトの中だけあります。

``` python
from pytransform import pyarmor_runtime
pyarmor_runtime()
```

難読化されたパッケージの場合、エントリースクリプトは `__init__.py` です。ブートストラップコードは、先頭の"`.`"で関連するインポートをすることができます。

``` python
from .pytransform import pyarmor_runtime
pyarmor_runtime()
```

また、難読化スクリプトとして実行時のパスが指定されている場合は、別の形態があります。

``` python
from pytransform import pyarmor_runtime
pyarmor_runtime('/path/to/runtime')
```

v5.8.7以降では、ランタイムパッケージにサフィックスが付く場合があります。たとえば、

``` python
from pytransform_vax_000001 import pyarmor_runtime
pyarmor_runtime(suffix='_vax_000001')
```

[スーパーモード](#super-mode) では、エントリースクリプトだけでなく、他の難読化スクリプトにも1行のBootstrap Codeが含まれています。

``` python
from pytransform import pyarmor
```

## ランタイムパッケージ

Runtime Packgeという難読化されたスクリプトと同じフォルダにあるpytransformというパッケージです。これは難読化されたスクリプトを実行するのに必要であり、難読化されたスクリプトの唯一の依存関係です。

一般にこのパッケージは難読化されたスクリプトと同じフォルダにありますが、どこに移動してもかまいません。どのPythonパスでも、このパッケージさえあれば、難読化されたスクリプトを通常のスクリプトとして実行することができます。また、同じ [Global Capsule](#グローバルカプセル) によって難読化されたすべてのスクリプトは、このパッケージを共有することができます。

このパッケージには2つのファイルが含まれています。

``` python
pytransform/
    __init__.py                  A normal python module
    _pytransform.so/.dll/.lib    A dynamic library implements core functions
```

v6.3.0以前は、2つのextraファイルがあります。

``` python
pytransform.key              Data file
license.lic                  The license file for obfuscated scripts
```

v5.7.0以前では、ランタイムパッケージは別の形式のRuntime Filesを持っています。

[スーパーモード](#super-mode) では、[ランタイムパッケージ](#ランタイムパッケージ) と [ランタイムファイル](#ランタイムファイル) の両方が拡張モジュールpytransformを参照するようになりました。異なるプラットフォームや異なるPythonのバージョンでは異なる名前を持っています。たとえば、

``` python
pytransform.pyd
pytransform.so
pytransform.cpython-38-darwin.so
pytransform.cpython-38-x86_64-linux-gnu.so
```

### ランタイムファイル
1つのパッケージではなく、2つに分かれたファイルになっています。

``` python
pytransform.py               A normal python module
_pytransform.so/.dll/.lib    A dynamic library implements core functions
```

v6.3.0以前は、2つのextraファイルがあります。

``` python
pytransform.key              Data file
license.lic                  The license file for obfuscated scripts
```

明らかに、ランタイムパッケージの方がランタイムファイルより明確です。

v5.8.7以降、ランタイムパッケージ（モジュール）にはサフィックスが付くことがあります。たとえば、

``` python
pytransform_vax_000001/
    __init__.py
    ...

pytransform_vax_000001.py
...
```

## 難読化スクリプトのライセンスファイル

特殊なランタイムファイル `license.lic` があり、難読化されたスクリプトを実行するために必要です。v6.3.0以降では、ダイナミックライブラリに埋め込むことができます。

`Pyarmor obfuscate` を実行すると、デフォルトのライセンスが生成され、どのマシンでも難読化スクリプトを実行できるようになり、有効期限もありません。

難読化されたスクリプトを固定マシンにバインドしたり、難読化されたスクリプトを期限付きにするには、コマンド `pyarmor licenses` を使用して新しい `license.lic` を生成し、デフォルトのものを上書きしてください。

特殊なランタイムファイル　 `license.lic` があり、難読化されたスクリプトを実行するために必要です。v6.3.0以降では、ダイナミックライブラリに埋め込むことができます。

:::note info
PyArmorでは、PyArmorのソースパス内にもう1つの `license.lic` が存在します。PyArmorを動かすのに必要なもので、me(PyArmor)が発行しています
:::

## 難読化されたスクリプトを使用する際のポイント

+ 難読化されたスクリプトは通常のpythonスクリプトですので、オリジナルのスクリプトを置き換えるためにシームレスに使用することができます。

+ 難読化されたスクリプトを実行したりインポートしたりする前に、 [ブートストラップコード](#ブートストラップコード) が実行されなければなリません。

+ [ランタイムパッケージ](#ランタイムパッケージ) は、 [ブートストラップコード](#ブートストラップコード) が正しく実行できるように、どのPythonパスにも含まれていなければなりません。これはpytransformという名前の通常のPythonパッケージで、Pythonのimportメカニズムによってインポートされるかもしれません。pytransformという名前のパッケージやモジュールがたくさんある場合、難読化されたスクリプトによって正しいパッケージがインポートされることを確認してください。スーパーモード用のランタイムパッケージと非スーパーモード用のランタイムパッケージは全く別物です。

以下の注意点は、スーパーモードでない場合のみ適用されます。

>+ [ブートストラップコード](#ブートストラップコード) は、ctypesによって動的ライブラリ_pytransform.so/.dll/.dylibをロードします。このファイルはプラットフォーム依存で、すべてのビルド済みダイナミック・ライブラリは、 [サポート・プラットフォーム](https://pyarmor.readthedocs.io/en/latest/platforms.html#support-platforms) のリストにあります。
>
>+ デフォルトでは、 [ブートストラップコード](#ブートストラップコード) は [ランタイムパッケージ](#ランタイムパッケージ) の中のダイナミックライブラリ `_pytransform` を検索します。詳細は `pytransform._load_library` を確認してください。
>
>+ 動的ライブラリ `_pytransform` が [ランタイムパッケージ](#ランタイムパッケージ) 内にない場合、 [ブートストラップコード](#ブートストラップコード) を変更します。
>
>``` python
>from pytransform import pyarmor_runtime
>pyarmor_runtime('/path/to/runtime')
>```
>+ `multiprocssing.Process`, `os.exec`, `subprocess.Popen` などで新しい Python インタプリタプロセスを起動する場合、難読化したスクリプトを実行する前に、新しいプロセスで [ブートストラップコード](#ブートストラップコード) が呼び出されることを確認してください。

より詳しい情報は、[Pythonスクリプトを難読化する方法](#pythonスクリプトを難読化する方法) と [難読化されたスクリプトの実行方法](#難読化されたスクリプトの実行方法) を参照してください。

## 難読化されたスクリプトの違いについて

Pythonスクリプトが難読化された後に変更されるものがあります。

+ 難読化されたスクリプトは、Pythonのメジャー／マイナーバージョンにバインドされます。例えば、Python 3.6で難読化されている場合、Python 3.6で動作する必要があります。Python 3.5では動きません。

+ 難読化されたスクリプトはプラットフォームに依存します、サポートプラットフォームは[Support Platforms](https://pyarmor.readthedocs.io/en/latest/platforms.html#support-platforms)にリストされています。

+ Pythonインタプリタが `Py_TRACE_REFS` または `Py_DEBUG`でコンパイルされている場合、難読化されたスクリプトを実行する際にクラッシュします。

+ `sys.settrace`, `sys.setprofile`, `threading.settrace`, `threading.setprofile` によって設定されたコールバック関数は、難読化されたスクリプトでは無視されます。この機能を利用したモジュールは動作しません。

+ 例えばinspectのようなモジュールが、難読化されたスクリプトのバイトコードやコードオブジェクトの属性を見ようとした場合、動作しない可能性があります。

+ 難読化されたコードオブジェクトを `cPickle` や3rdパーティのシリアライズツールで渡すと、動作しない可能性があります。

+ 難読化されたスクリプトは実行中のフレームを複製するため、 `sys._getframe([n])` が異なるフレームを取得する可能性があります。

+ 例外が発生した場合、トレースバックの行番号が元のスクリプトと異なる可能性があります（特にこのスクリプトがプラグインスクリプトやクロスプロテクションコードによってパッチされている場合）。

+ 難読化されたスクリプトのコードオブジェクトの `__file__` 属性は、実際のファイル名ではなく、<frozen name> になります。そのため、トレースバックでは、ファイル名が<frozen name>と表示されます。
モジュール属性 `__file__` は `filename` のままであることに注意してください。例えば、foo.pyというスクリプトを難読化し実行すると、
>``` python
>def hello(msg):
>    print(msg)
>
># The output will be 'foo.py'
>print(__file__)
>
># The output will be '<frozen foo>'
>print(hello.__file__)
>```
>
>スーパーモードでは、引数がない場合、ビルドイン関数 dirs(), vars() は動作しませんので、この方法で呼び出してください。
>
>``` python
>dirs() => sorted(locals().keys())
>vars() => locals()
>```
>なお、dirs(x), vars(x) は x が None でない場合にも動作します。

## 3rdパーティインタプリターについて
Jythonなどのサードパーティーインターパーターや、Python C/C++の組み込みコードについては、難読化したスクリプトを実行するためには少なくとも以下の条件を満たしている必要があります。

+ Pythonのダイナミックライブラリをロードし、ソース `https://github.com/python/cpython` からビルドし、コアとなるソースコードに手を加えてはいけません。

+ Linuxでは、RTLD_GLOBALが `dlopen`で `libpythonXY.so` をロードするように設定されていなければ、難読化したスクリプトは動作しません。

:::note info
Boost::python はデフォルトでは libpythonXY.so を RTLD_GLOBAL でロードしないため、難読化されたスクリプトを実行すると "No PyCode_Type found" というエラーが発生します。この問題を解決するには、初期化時に sys.setdlopenflags(os.RTLD_GLOBAL) メソッドを呼び出すようにしてください。
:::

+ モジュール `ctypes` が存在し、 `ctypes.pythonapi._handle` がPythonダイナミックライブラリの実際のハンドルとして設定されている必要があります。
PyArmorはこのハンドルを使っていくつかのPython C APIに問い合わせを行います。

PyPyはCPythonとは全く異なるので、pyarmorとは動作しません。

# 10.PyArmorの仕組み

`foo.py` がPyArmorによって難読化された後、何が起こったか見てみましょう。以下は、出力パスの `dist` にあるファイルのリストです。

``` python
foo.py

pytransform/
    __init__.py
    _pytransform.so, or _pytransform.dll in Windows, _pytransform.dylib in MacOS
    pytransform.key
    license.lic
```

`dist/foo.py` は難読化されたスクリプトで、内容は以下の通りです。

``` python
from pytransform import pyarmor_runtime
pyarmor_runtime()
__pyarmor__(__name__, __file__, b'\x06\x0f...')
```

[ランタイムパッケージ](#ランタイムパッケージ) と呼ばれる extraフォルダに `pytransform` があり、これが難読化されたスクリプトを実行したりインポートするのに必要なものです。このパッケージがPythonのPathにある限り、難読化されたスクリプト `dist/foo.py` は通常のPythonスクリプトとして使用することができます。つまり<font color =green>元のPythonスクリプトは難読化されたスクリプトにシームレスに置き換えることができます。</font>

## Pythonスクリプトを難読化する方法

PyArmorでPythonスクリプトを難読化するには？

最初に、Pythonスクリプトをコードオブジェクトにコンパイルします。

``` python
char *filename = "foo.py";
char *source = read_file( filename );
PyCodeObject *co = Py_CompileString( source, "<frozen foo>", Py_file_input );
```

次に、コードオブジェクトを次のように変更します。

+ バイトコード `co_code` を `try...finally` ブロック内でラップする。

``` python
wrap header:

        LOAD_GLOBALS    N (__armor_enter__)     N = length of co_consts
        CALL_FUNCTION   0
        POP_TOP
        SETUP_FINALLY   X (jump to wrap footer) X = size of original byte code

changed original byte code:

        Increase oparg of each absolute jump instruction by the size of wrap header

        Obfuscate original byte code

        ...

wrap footer:

        LOAD_GLOBALS    N + 1 (__armor_exit__)
        CALL_FUNCTION   0
        POP_TOP
        END_FINALLY
```

+ `co_consts` に関数名 `__armor_enter__`, `__armor_exit__` を追加する
+ `co_stacksize` を2倍にする
+ `co_flags` に `CO_OBFUSCAED(0x80000000)`フラグを設定する
+ `co_const` にあるすべてのコードオブジェクトを再帰的に変更する

次に、再構成されたコードオブジェクトをシリアライズし、定数やリテラル文字列を保護するために難読化します。

``` python
char *string_code = marshal.dumps( co );
char *obfuscated_code = obfuscate_algorithm( string_code  );
```

最後に難読化されたスクリプトを生成します。


``` python
sprintf( buf, "__pyarmor__(__name__, __file__, b'%s')", obfuscated_code );
save_file( "dist/foo.py", buf );
```

難読化されたスクリプトは、通常のPythonスクリプトで、次のようなものです。


``` python
__pyarmor__(__name__, __file__, b'\x01\x0a...')
```

## プラグインとの付き合い方

PyArmorでは、プラグインを使用して、スクリプトが難読化される前に、Pythonコードを難読化されたスクリプトに挿入します。したがって、難読化されたスクリプトの実行中にプラグインコードを実行できます。たとえば、プラグインを使用してインターネット時間を確認します。

``` python
pyarmor obfuscate --plugin check_ntp_time foo.py
```

なぜプラグインコードを直接スクリプトに挿入しないのでしょうか？
なぜなら、それらのほとんどは難読化されたスクリプトの中で呼び出されなければならないからです。例えば、難読化されたスクリプトのライセンス情報を取得します。

各プラグインは通常のPythonスクリプトであり、PyArmorはこの方法で検索を行います。
+ もしプラグインが絶対パスであれば、対応する `.py` ファイルを正確に探します。
+ 相対パスの場合は、 `.py` ファイルを検索します。
>+ 現在のパス
>+ $HOME/.pyarmor/plugins
>+ {pyarmor_folder}/plugins
+ 見つからない場合は例外を発生させる

スクリプトを難読化するプラグインが指定されている場合、各コメント行をスキャンしてプラグインマーカーを探します。

プラグインマーカーには3種類あります。
+ Plugin Definition Marker
+ Plugin Inline Marker
+ Plugin Call Marker

Plugin Definition Markerは、次のようなものです。

``` python
# {PyArmor Plugins}
```

通常、1つのスクリプトの中に1つだけ存在し、すべてのプラグインはここに注入されます。これは1行のコメントでなければならず、インデントしてはいけません。プラグイン定義マーカーがない場合は、どのプラグインも注入されません。

その他は、主にプラグインスクリプトで定義された関数を呼び出すために使用されます。3つの形式があり、この接頭辞を持つコメント行はすべてプラグインマーカーとなります。

``` python
# PyArmor Plugin:
# pyarmor_
# @pyarmor_
```

これらは何度も現れる可能性があり、どのようなインデントでも構いませんが、一般的にはプラグイン定義マーカーの後ろにあるべきです。

Plugin Inline Marker と呼ばれる最初の形式は、PyArmorがこのパターンとそれに続く1つの空白を正確に削除し、残りの部分をそのまま残すだけです。例えば、これらはスクリプトfoo.pyのインラインマーカです。

``` python
# PyArmor Plugin: check_ntp_time()
# PyArmor Plugin: print('This is plugin code')
# PyArmor Plugin: if sys.flags.debug:
# PyArmor Plugin:     check_something():
```

`dist/foo.py` では、次のように置き換わります。

``` python
check_ntp_time()
print('This is plugin code')
if sys.flags.debug:
    check_something()
```

コマンドラインでプラグインが指定されている限り、これらの置き換えが行われます。外部プラグインスクリプトがない場合は、コマンドラインで特別なプラグイン名を使用します。たとえば、

```
pyarmor obfuscate --plugin on foo.py
```

2つ目の形式はPlugin call Marker と呼ばれ、プラグインスクリプトで定義された関数を呼び出すためにのみ使用されます。また、この関数名がプラグイン名として指定されていない場合、PyArmorはこのマーカーに触れることはありません。例えば、次のコマンドでスクリプトを難読化します。

``` python
pyarmor obfuscate --plugin check_ntp_time foo.py
```

`foo.py` では、関数名 `check_multi_mac` としてコマンドラインにプラグイン名が指定されていないため、最初のマーカーだけが処理され、2番目のマーカーはそのまま維持されます。

``` python
# pyarmor_check_ntp_time()
# pyarmor_check_multi_mac()

==>

check_ntp_time()
# pyarmor_check_multi_mac()
```

最後の `# @pyarmor_` は、2番目の形式とほぼ同じですが、コメントのプレフィックスが `@` に置き換えられ、主にデコレータを注入するために使用されます。たとえば、

``` python
# @pyarmor_assert_obfuscated(foo.connect)
def login(user, name):
    foo.connect(user, name)

==>

@assert_obfuscated(foo.connect)
def login(user, name):
    foo.connect(user, name)
```

プラグイン名の先頭に `@` がある場合、そのプラグインがスクリプト内で使用されているときのみスクリプトに注入され、そうでないときは無視されます。たとえば、

``` python
pyarmor obfuscate --plugin @check_ntp_time foo.py
```

`foo.py`スクリプトは、プラグイン関数 `check_ntp_time`をPlugin Call Markerで呼び出す必要があります。たとえば、

``` python
# pyarmor_check_ntp_time()
```

Plugin Inline Markerでは動作しません。たとえば、

``` python
# PyArmor Plugin: check_ntp_time()
```

このマーカーさえも `check_ntp_time()` に置き換えられますが、プラグインスクリプトは難読化されたスクリプトにインジェクションされることはありません。実行すると、関数 `check_ntp_name` が見つからないと文句を言われます。

:::note info
コマンドラインに `--plugin` オプションがない場合、 pyarmor はコメント中のプラグインマーカーを検索しません。外部のプラグインスクリプトがない場合、以下のように特別な名前を使用します。

``` python
pyarmor obfuscate --plugin on foo.py
```
:::

## エントリースクリプトの特殊な取り扱いについて

エントリースクリプトには、2つの特別な変更があります。

+ 難読化する前に、エントリースクリプトにプロテクションコードを挿入します。
+ 難読化した後、難読化したスクリプトにブートストラップコードを挿入します。

エントリスクリプトを難読化する前に、PyArmorはその内容を一行ずつ検索します。もし次のような行があれば、

``` python
# {PyArmor Protection Code}
```

PyArmorはこの行を保護コードに置き換えます。

もしこのような行があれば、

``` python
# {No PyArmor Protection Code}
```

PyArmorはこのスクリプトにパッチを当てません。

もし両方の行が見つからない場合は、その行の前に保護コードを挿入してください。

``` python
if __name__ == '__main__'
```

`__main__` 行が見つからない場合は、何もしません。

これは、保護コードのデフォルトテンプレートです。

``` python
def protect_pytransform():

    import pytransform

    def check_obfuscated_script():
        CO_SIZES = 49, 46, 38, 36
        CO_NAMES = set(['pytransform', 'pyarmor_runtime', '__pyarmor__',
                        '__name__', '__file__'])
        co = pytransform.sys._getframe(3).f_code
        if not ((set(co.co_names) <= CO_NAMES)
                and (len(co.co_code) in CO_SIZES)):
            raise RuntimeError('Unexpected obfuscated script')

    def check_mod_pytransform():
        def _check_co_key(co, v):
            return (len(co.co_names), len(co.co_consts), len(co.co_code)) == v
        for k, (v1, v2, v3) in {keylist}:
            co = getattr(pytransform, k).{code}
            if not _check_co_key(co, v1):
                raise RuntimeError('unexpected pytransform.py')
            if v2:
                if not _check_co_key(co.co_consts[1], v2):
                    raise RuntimeError('unexpected pytransform.py')
            if v3:
                if not _check_co_key(co.{closure}[0].cell_contents.{code}, v3):
                    raise RuntimeError('unexpected pytransform.py')

    def check_lib_pytransform():
        filename = pytransform.os.path.join({rpath}, {filename})
        size = {size}
        n = size >> 2
        with open(filename, 'rb') as f:
            buf = f.read(size)
        fmt = 'I' * n
        checksum = sum(pytransform.struct.unpack(fmt, buf)) & 0xFFFFFFFF
        if not checksum == {checksum}:
            raise RuntimeError("Unexpected %s" % filename)
    try:
        check_obfuscated_script()
        check_mod_pytransform()
        check_lib_pytransform()
    except Exception as e:
        print("Protection Fault: %s" % e)
        pytransform.sys.exit(1)

protect_pytransform()
```

すべての文字列テンプレート `{xxx}` は、PyArmor によって実際の値に置き換えられます。

PyArmor がこの保護コードを挿入するのを防ぐには、スクリプトを難読化する際に `--no-cross-protection` を渡してください。

エントリースクリプトが難読化された後、ブートストラップコードが難読化されたスクリプトの先頭に挿入されます。

## 難読化されたスクリプトの実行方法

難読化されたスクリプト `dist/foo.py` を `Python Interpreter` で実行するには？

最初の2行は [ブートストラップコード](#ブートストラップコード) と呼ばれています。

``` python
from pytransform import pyarmor_runtime
pyarmor_runtime()
```

これは以下のタスクを実行します。

+ `ctpes` によって動的ライブラリ`_pytransform`をロードします
+ license.licが有効かどうかをチェックします
+ ビルドインモジュールに3つのcfunctionsを追加します。　`pyarmor__` , `__armor_enter__` , `__armor_exit__` 


`dist/foo.py` の次のコード行です。

``` python
__pyarmor__(__name__, __file__, b'\x01\x0a...')
```

`__pyarmor__` が呼ばれると、難読化されたコードから元のモジュールを `import` します。


``` python
static PyObject *
__pyarmor__(char *name, char *pathname, unsigned char *obfuscated_code)
{
    char *string_code = restore_obfuscated_code( obfuscated_code );
    PyCodeObject *co = marshal.loads( string_code );
    return PyImport_ExecCodeModuleEx( name, co, pathname );
}
```

その後、このpythonインタプリタのランタイムで処理されます。

+ コードオブジェクトが実行されると同時に `__armor_enter__` が呼び出され、このコードオブジェクトのバイトコードが復元されます。

``` python
static PyObject *
__armor_enter__(PyObject *self, PyObject *args)
{
    // Got code object
    PyFrameObject *frame = PyEval_GetFrame();
    PyCodeObject *f_code = frame->f_code;

    // Increase refcalls of this code object
    // Borrow co_names->ob_refcnt as call counter
    // Generally it will not increased  by Python Interpreter
    PyObject *refcalls = f_code->co_names;
    refcalls->ob_refcnt ++;

    // Restore byte code if it's obfuscated
    if (IS_OBFUSCATED(f_code->co_flags)) {
        restore_byte_code(f_code->co_code);
        clear_obfuscated_flag(f_code);
    }

    Py_RETURN_NONE;
}
```

+ `__armor_exit__` は、コードオブジェクトの実行が完了する限り呼び出され、再びバイトコードを難読化します。

``` python
static PyObject *
__armor_exit__(PyObject *self, PyObject *args)
{
    // Got code object
    PyFrameObject *frame = PyEval_GetFrame();
    PyCodeObject *f_code = frame->f_code;

    // Decrease refcalls of this code object
    PyObject *refcalls = f_code->co_names;
    refcalls->ob_refcnt --;

    // Obfuscate byte code only if this code object isn't used by any function
    // In multi-threads or recursive call, one code object may be referenced
    // by many functions at the same time
    if (refcalls->ob_refcnt == 1) {
        obfuscate_byte_code(f_code->co_code);
        set_obfuscated_flag(f_code);
    }

    // Clear f_locals in this frame
    clear_frame_locals(frame);

    Py_RETURN_NONE;
}
```

## 難読化されたスクリプトのパック方法

PyArmorによって生成された難読化スクリプトはPythonスクリプトをシームレスに置き換えることができますが、PyInstallerによってそれらを1つのバンドルにパックするときに問題があります。

<font color=blue>難読化されたスクリプトのすべての依存関係がまったく検出されない</font>

この問題を解決するために、一般的な解決策は、

1. 元のスクリプトの依存関係をすべて見つける
2. 難読化されたスクリプトが必要とするランタイムファイルをバンドルに追加する
3. バンドル内のオリジナルスクリプトを難読化スクリプトに置き換える
4. エントリスクリプトを難読化スクリプトに置き換える

PyArmorはこれを実現するための [pack](https://pyarmor.readthedocs.io/en/latest/man.html#pack) コマンドを提供しています。しかし、場合によってはうまくいかないこともあります。このドキュメントでは、 `pack` コマンドが何をするのかを説明し、またあなた自身で難読化スクリプトをバンドルするためのガイドとなります。

まず　`pyinstaller` をインストールします。

``` python
pip install pyinstaller
```

そして、難読化したスクリプトを `dist/obf` におきます。

``` python
pyarmor obfuscate --output dist/obf --package-runtime 0 hello.py
```

次に `specfile` を生成し、難読化されたスクリプトが必要とするランタイムファイルを追加します。

``` python
pyi-makespec --add-data dist/obf/license.lic:. \
             --add-data dist/obf/pytransform.key:. \
             --add-data dist/obf/_pytransform.*:. \
             -p dist/obf --hidden-import pytransform \
             hello.py
```

スーパーモードによりスクリプトが難読化された場合は、

``` python
pyarmor obfuscate --output dist/obf --advanced 2 --package-runtime 0 hello.py
```

このコマンドで `.spec` ファイルを生成します。

``` python
pyi-makespec -p dist/obf --hidden-import pytransform hello.py
```

>Windowsの場合、コマンドラインの:は;に置き換えてください。

そして、specfile hello.specにパッチを当てます、Analysisオブジェクトの後に以下の行を挿入してください。この目的は、オリジナルのスクリプトをすべて難読化したものに置き換えることです。

``` python
src = os.path.abspath('.')
obf_src = os.path.abspath('dist/obf')

for i in range(len(a.scripts)):
    if a.scripts[i][1].startswith(src):
        x = a.scripts[i][1].replace(src, obf_src)
        if os.path.exists(x):
            a.scripts[i] = a.scripts[i][0], x, a.scripts[i][2]

for i in range(len(a.pure)):
    if a.pure[i][1].startswith(src):
        x = a.pure[i][1].replace(src, obf_src)
        if os.path.exists(x):
            if hasattr(a.pure, '_code_cache'):
                with open(x) as f:
                    a.pure._code_cache[a.pure[i][0]] = compile(f.read(), a.pure[i][1], 'exec')
            a.pure[i] = a.pure[i][0], x, a.pure[i][2]
```

パッチを適用した specfile を実行し、最終的なディストリビューションを構築します。

``` python
pyinstaller --clean -y hello.spec
```

:::note info
オプション `--clean` が必要です。そうしないと、キャッシュされた `.pyz` が使用されるため、難読化されたスクリプトは置換されません。
:::

難読化されたスクリプトの動作を確認します。

``` python
dist/hello/hello.exe
```

# 11.ランタイムモジュールpytransform 

難読化されたスクリプトがエンドユーザーにとってブラックボックスであることに気づいたなら、あなた自身のPythonスクリプトでもっとできることがあります。このような場合、`pytransform` が役に立つでしょう。

`pytransform` モジュールは難読化されたスクリプトと一緒に配布され、難読化されたスクリプトを実行する前にインポートする必要があります。また、あなたのPythonスクリプトで使用することもできます。

## contents

| | 
|:-----------|
| _exception_  PytransformError | 


非推奨です。
これは、pytransform api が失敗したときに発生します。例外の引数は、エラーの原因を示す文字列です。

スーパーモードでは使用できません。

| | 
|:-----------|
| get_expired_days() | 

時間制限のあるライセンスがあと何日残っているかを返します。

`>0`: この日数で有効
`-1`: 有効期限は切れていない

:::note info
難読化されたスクリプトが期限切れになった場合、例外を発生させて直接終了します。難読化されたスクリプト内のすべてのコードは実行されないので、この関数が `0` を返すことはありません。
:::

| | 
|:-----------|
| get_license_info() | 

難読化されたスクリプトのライセンス情報を取得します。

キーを含むdictを返します。

+ ISSUER: 発行者ID
+ EXPIRED: 期限切れの日付
+ IFMAC: このライセンスにバインドされているマックアドレス
+ HARDDISK: このライセンスにバインドされているハードディスクのシリアル番号
+ IPV4: このライセンスにバインドされているIPV4アドレス
+ DATA: このライセンスに保存されている追加データ、ライセンスタイプの拡張に使用されます
+ CODE: このライセンスの登録コード

Noneは、ライセンスにこのキーがないことを意味します。

キー `ISSUER` は、v6.2.5から導入されました。試用版Pyarmorでlicense.licを生成した場合は `trial` になります。購入したpyarmorの場合は、 `pyarmor-vax-NNNNN` のように購入したキーになります。v6.0.1以前のpyarmorで生成された `license.lic` は、 `None` になることに注意してください。

ライセンスが無効な場合(例えば、有効期限が切れているなど)は、　`Exception` を発生させます。

| | 
|:-----------|
| get_license_code() | 

非スーパーモードでは文字列、スーパーモードではバイトオブジェクトを返します。これは、難読化されたスクリプトのライセンスを生成するための最後の引数です。

ライセンスが無効な場合は、`Exception` を発生させます。

| | 
|:-----------|
| get_user_data() |

非スーパーモードの文字列またはスーパーモードのbytesオブジェクトを返します。これは、難読化されたスクリプトのライセンスを生成するものとして `-x` で指定されます。
`-x` の指定がない場合は None を返します。

ライセンスが無効な場合は、`Exception` を発生させます。

| | 
|:-----------|
| get_hd_info(hdtype, name=None) |

`hdtype` ごとにハードウェア情報を取得します。 `hdtype` は以下のいずれかです。

`HT_HARDDISK` は、最初のハードディスクのシリアル番号を返します。
`HT_IFMAC` は、最初のネットワークカードの mac アドレスを返します
`HT_IPV4h` は、最初のネットワークカードの IPv4 アドレスを返します
`HT_DOMAIN` は、ターゲットマシンのドメイン名を返します

何か問題がある場合は Exception を発生させます。

Linuxでは、ネットワークカードやハードディスクの名前を取得するために、`name` を使用します。たとえば、

```python
get_hd_info(HT_IFMAC, name="eth2")
get_hd_info(HT_HARDDISK, name="/dev/vda2")
```

Windowsでは、ネットワークカードやハードディスク（すべて）を取得するために、nameが使用されます。たとえば、

``` python
get_hd_info(HT_IFMAC, name="*")
get_hd_info(HT_HARDDISK, name="*")

get_hd_info(HT_HARDDISK, name="/0")    # First disk
get_hd_info(HT_HARDDISK, name="/1")    # Second disk
```

:::note info
v6.5.3での変更点

新しいキーワードパラメータ `name` を追加
キーワードパラメータ `size` を削除
:::

| | 
|:-----------|
| HT_HARDDISK, HT_IFMAC, HT_IPV4, HT_DOMAIN |

`get_hd_info()` を呼び出す際の `hdtype` の定数。

| | 
|:-----------|
| assert_armored(*args)|

引数のモジュール/関数/メソッドのリストをチェックするためのデコレータ関数が難読化されています。

モジュールや関数、メソッドをチェックしますが、例えば `Class` がサポートしていないような型はチェックできません。関数がビルドインデコレータ、例えば `@staticmethod` によってデコレートされている場合、難読化されていないものとして扱われます。

難読化されていないものがあれば、RuntimeErrorを発生させます。

たとえば、

``` python
import foo

from pytransform import assert_armored
@assert_armored(foo, foo.connect, foo.connect2)
def start_server():
    foo.connect('root', 'root password')
```

:::note info
v6.6.2以降、チェックモジュールがサポートされましたが、スーパーモードのみです
:::

| | 
|:-----------|
| check_armored(*args)|

argsに含まれるすべての関数/メソッド/モジュールが難読化されている場合、Trueを返します。

難読化されていないものがあれば、Falseを返します。

モジュール、関数、メソッドをチェックすることができます。　その他の型、例えばClassはサポートしていませんいないものを。関数が、例えば `@staticmethod` のようなビルドインデコレーターによってデコレートされている場合、難読化されていないとみなされ、Falseが返されます。

たとえば、

``` python
import foo

from pytransform import check_armored
if not check_armored(foo, foo.connect, foo.connect2):
    print('My script is hacked')
```

:::note info
v6.6.2のスーパーモードのみの新機能です。
:::

## Examples

これらのサンプルコードを任意のスクリプト、例えば `foo.py` にコピーして難読化し、難読化されたスクリプトを実行してください。

ライセンスの残り日数を表示します。

``` python
from pytransform import get_license_info, get_expired_days
try:
    code = get_license_info()['CODE']
    left_days = get_expired_days()
    if left_days == -1:
        print('This license for %s is never expired' % code)
    else:
        print('This license for %s will be expired in %d days' % (code, left_days))
except Exception as e:
    print(e)
```

その他の使用方法については、 [プラグインを使用したライセンスタイプの拡張](#プラグインを使用したライセンスタイプの拡張) と [プラグインを使ったセキュリティの向上](#プラグインを使ったセキュリティの向上) を参照してください。

:::note info
難読化されたスクリプトを実行するとき、`pytransform.py` は難読化されませんが、PyArmorによって保護されています。もしそれが変更されると、難読化されたスクリプトは保護例外を発生させます。

[エントリースクリプトの特殊な取り扱いについて](#エントリースクリプトの特殊な取り扱いについて) を参照してください。
:::

# 12.Support Platforms

※[公式ドキュメントを参照してください](https://pyarmor.readthedocs.io/en/latest/platforms.html)

# 13.難読化したスクリプトのモード

PyArmorは、セキュリティとパフォーマンスのバランスをとるために、多くのモードでスクリプトを難読化することができます。ほとんどの場合、デフォルトのモードで問題なく動作します。しかし、パフォーマンスがボトルブロックになるような場合、あるいは特殊なケースでは、これらのモードの違いを理解し、異なるモードでスクリプトを難読化し、希望通りに動作させる必要があるかもしれません。

## Super Mode

この `Super Mode`モードでは、PyCode_Typeの構造が変更され、バイトコードまたはワードコードがマッピングされます。必要なランタイムファイルは `pytransform` 拡張子のみで、難読化されたスクリプトの形式はユニークで、ユーザーを混乱させる可能性のあるいわゆる [ブートストラップコード](#ブートストラップコード) はありません。難読化されたスクリプトはすべて次のようなものです。

``` python
rom pytransform import pyarmor
pyarmor(__name__, __file__, b'\x0a\x02...', 1)
```

このモードは、適切な場合に有効化することをお勧めします。現在、最新のPythonのバージョンにのみ対応しています。

+ Python 2.7
+ Python 3.7
+ Python 3.8
+ Python 3.9

この機能を有効にするには、オプション `--advanced 2` を[obfuscate](https://pyarmor.readthedocs.io/en/latest/man.html#obfuscate) に設定します。

``` python
pyarmor obfuscate --advanced 2 foo.py
```

詳しい使い方は、 [スーパーモードの使用](#スーパーモードの使用) をご覧ください。

:::note info
スーパーモード難読化スクリプトとそうでないものを混在させて場合、動作しません。
:::

## Super Plus Mode

これはスーパーモードの拡張で、いくつかの関数をバイナリコードに変換するものです。PyArmor 7.0.1 で導入され、現在では `arch X86_64`と Python 3.7, 3.8, 3.9 でのみ動作しています。PyArmor 7.5.0 からは、`arch X86_64` の Python 3.10 と、Darwin と Linux の `arch 
AARCH64` の Python 3.7~3.10 が動作するようになりました。

cコンパイラが必要です。LinuxとDarwinでは、`gcc` と `clang` でOKです。Windowsでは、`clang.exe` のみ動作します。以下のいずれかの方法で設定することができます。

+ clang.exeがあれば、任意のパスで実行できればOKです
+ Windows版LLVMのダウンロードとインストールを行います
+ https://pyarmor.dashingsoft.com/downloads/tools/clang-9.0.zip をダウンロードします。約26Mバイトで、中に入っているファイルは1つだけでです。解凍して `clang.exe` を `$HOME/.pyarmor/` に保存します。 `$HOME` は現在のログオンユーザーのホームパスです。実際のパスは環境変数 `HOME` で確認してください。

cコンパイラが動作したら、`--advanced 5` で Super Plus Mode を有効にします。

``` python
pyarmor obfuscate --advanced 5 foo.py
```

モジュール内の一部の関数だけが spp モードで難読化され、それ以外は super モードで難読化されたままです。sppモードでサポートされていない機能を使用している関数は自動的に無視されます。super plusモードでこのモジュールに何か問題がある場合、モジュールの先頭に1行挿入して手動で無視するようにしてください。

``` python
# pyarmor options: no-spp-mode
```

スーパープラスモードでは、最初の行からスキャンし、空行を無視し、`#` で始まる行を解析し、それ以外の行のスキャンを停止します。 `pyarmor` のオプションで始まる行があれば、それ以降のオプションを読みます。また、`function` や `class` などを無視するために、docstringで動作します。たとえば、

``` python
def foo(a, b):
    '''pyarmor options: no-spp-mode'''
    pass
```

sppモードではいくつかの違いがあります。

+ 例外ハンドラにない引数で `raise` を呼び出すと、異なるExceptionが発生します。

``` python
>>> raise
RuntimeError: No active exception to reraise

# In spp mode
>>> raise
UnboundlocalError: local variable referenced before assignment
```

sppモードのサポートされていない機能、

``` python
unsupport_nodes = (
    ast.Nonlocal,
    ast.AsyncFunctionDef, ast.AsyncFor, ast.AsyncWith,
    ast.Await, ast.Yield, ast.YieldFrom, ast.GeneratorExp,

    ast.NamedExpr,

    ast.MatchValue, ast.MatchSingleton, ast.MatchSequence,
    ast.MatchMapping, ast.MatchClass, ast.MatchStar,
    ast.MatchAs, ast.MatchOr
)
```

そしてサポートされていない機能、

``` python
exec, eval, super, locals, sys._getframe
```

例えば、以下のような関数は、サポートされていない機能やサポートされていない関数を使用しているため、スーパープラスモードでは難読化されません。

``` python
async def nested():
    return 42

def foo1():
    for n range(10):
        yield n

def foo2():
   frame = sys._getframe(2)
   print('parent frame is', frame)
```

:::note info
体験版では、スーパープラスモードはご利用いただけません。
:::

## Advanced Mode

このアドバンスドモードという機能は、PyArmor 5.5.0から導入されました。このモードでは、セキュリティを向上させるためにPyCode_Typeの構造が少し変更されます。また、変更されたコードオブジェクトが正常に実行できるように、Pythonインタープリタにフックが注入されます。
また、Python CのコアAPIが予期せず変更された場合、アドバンストモードの難読化スクリプトは動作しなくなります。この機能はマシンの命令セットに大きく依存するため、現在は x86/x64 アーキでのみ利用可能です。 また、Pythoninterpreterが古いgccや他のCコンパイルでコンパイルされている場合、pyarmorは間違いを起こす可能性があります。もし、Python インタープリタが advanced モードで動作しない場合、その問題を報告していただければ幸いです。

このことを考慮し、デフォルトではアドバンスドモードは無効になっています。有効にするには、[obfuscate](https://pyarmor.readthedocs.io/en/latest/man.html#obfuscate) コマンドにオプション `--advanced` を渡します。

``` python
pyarmor obfuscate --advanced 1 foo.py
```

### アップグレードの注意点

アップグレード前に、製品環境のPythonインタプリタを試用し、アドバンスドモードで動作することを確認してください。以下はそこの手順があります。

[https://github.com/dashingsoft/pyarmor-core/tree/v5.3.0/tests/advanced_mode/README.md](https://github.com/dashingsoft/pyarmor-core/tree/v5.3.0/tests/advanced_mode/README.md)

次のマイナーバージョンでアップグレードすることをお勧めします。

:::note info
試用版では、このモジュール内に約30以上の function がある場合、アドバンスドモードでは難読化できません（アドバンスドモード以外では難読化可能です）。
:::

:::note warn
Python3.9では、advanced modeはサポートされていません。super modeが動作するPythonのバージョンでは、super modeを使用することが推奨されます。
:::

## VM Mode

VMモードは6.3.3から導入されました。VMモードはコードの仮想化に基づいており、強力なvmツールを使ってダイナミックライブラリのコアアルゴリズムを保護します。このモードはアドバンスドモードとスーパーモードを強化したものです。

アドバンスドモードと一緒にVMモードを有効にするには、このようにします。

``` python
pyarmor obfuscate --advanced 3 foo.py
```

この方法で、スーパーモードでvmモードを有効にします。

``` python
pyarmor obfuscate --advanced 4 foo.py
```

vmモードでは、セキュリティは格段に向上しますが、ダイナミックライブラリのサイズが大きくなり、パフォーマンスが低下します。オリジナルのサイズは600K〜800K程度のもので、vmモードでは4M程度になります。性能については、[難読化されたスクリプトのパフォーマンス](#14難読化されたスクリプトのパフォーマンス) を参照してテストしてください。

## Obfuscating Code Mode

Python モジュールファイルでは、一般に多くの関数があり、各関数はコードオブジェクトを持っています。

+ obf_code == 0
各関数のコードオブジェクトは、そのまま維持されます。

+ obf_code == 1 (デフォルト)
この場合、各関数のコード・オブジェクトはラップ・モードに応じて異なる方法で難読化されます。

+ obf_code == 2
obf_mode 1 とほぼ同じですが，より複雑なアルゴリズムでバイトコードを難読化するため，前者より遅くなります。

## Wrap Mode

:::note info
スーパーモードの場合はラップモードは常に有効になり、スーパーモードでは無効にすることはできません。
:::

+ wrap_mode == 0
ラップモードがオフの場合、各関数のコードオブジェクトはこのような形で難読化されます。

``` python
0   JUMP_ABSOLUTE            n = 3 + len(bytecode)

3    ...
     ... Here it's obfuscated bytecode of original function
     ...

n   LOAD_GLOBAL              ? (__armor__)
n+3 CALL_FUNCTION            0
n+6 POP_TOP
n+7 JUMP_ABSOLUTE            0
```

このコードオブジェクトが最初に呼ばれたとき

1. 最初の命令は JUMP_ABSOLUTE で、これはオフセット n にジャンプします。
2. オフセットnで、PyCFunction __armor__を呼び出すように指示されます。この関数は、オフセット3からnまでの難読化されたバイトコードを復元し、元のバイトコードをオフセット0に移動させます。
3. 関数呼び出しの後、最後の命令はオフセット0にジャンプすることです。 これで本当のバイトコードが実行されます。

最初の呼び出しの後、この関数は元の関数と同じになります。

+ wrap_mode == 1 (デフォルト)
ラップモードがオンの場合，各関数のコードオブジェクトは `try...finally` ブロックによってラップされます．

``` python
LOAD_GLOBALS    N (__armor_enter__)     N = length of co_consts
CALL_FUNCTION   0
POP_TOP
SETUP_FINALLY   X (jump to wrap footer) X = size of original byte code

Here it's obfuscated bytecode of original function

LOAD_GLOBALS    N + 1 (__armor_exit__)
CALL_FUNCTION   0
POP_TOP
END_FINALLY
```

このコードオブジェクトが毎回呼び出されるとき、

1. `__armor_enter__` が難読化されたバイトコードを復元する
2. 本当の関数コードを実行する
3. 最後のブロックでは、 `__armor_exit__` が再びバイトコードを難読化します。

## Obfuscating module Mode

+ obf_mod == 1
最終的に難読化されたスクリプトは次のようになります。

``` python
__pyarmor__(__name__, __file__, b'\x02\x0a...', 1)
```

第3パラメータは、Pythonスクリプトのコードオブジェクトをシリアライズしたものです。このように生成されます。

``` python
PyObject *co = Py_CompileString( source, filename, Py_file_input );
obfuscate_each_function_in_module( co, obf_mode );
char *original_code = marshal.dumps( co );
char *obfuscated_code = obfuscate_whole_module( original_code  );
sprintf( buffer, "__pyarmor__(__name__, __file__, b'%s', 1)", obfuscated_code );
```

+ obf_mod == 2 (デフォルト)
異なる暗号化アルゴリズムを使用し、セキュリティを強化し、より高速にします。v6.3.0以降の新機能です。

+ obf_mod == 0
このモードでは、シリアル化されたモジュールをそのまま維持するための最後のステートメントは次のようになります。

``` python
sprintf( buffer, "__pyarmor__(__name__, __file__, b'%s', 0)", original_code );
```

そして、最終的な難読化されたスクリプトは、次のようになります。

``` python
__pyarmor__(__name__, __file__, b'\x02\x0a...', 0)
```

これらのモードはすべて、現時点ではプロジェクトでのみ変更できます。 [異なるモードによるスクリプトの難読化](#異なるモードによるスクリプトの難読化) を参照してください。

## Restrict Mode

難読化されたスクリプトは、それぞれ独自の制限モードを持っており、このスクリプトの使用を制限するために使用されます。難読化されたモジュールをインポートして、関数や属性を使用する場合、最初に制限モードがチェックされ、制限モードに違反した場合、保護例外が発生します。

5つの制限モードがあり、モード2と3はスタンドアロンのスクリプトのみ、モード4は主に難読化されたパッケージ、モード5はその両方に適用されます。

+ モード1
このモードでは、難読化されたスクリプトを全く変更することができません。例えば、難読化されたスクリプト`foo.py` の末尾にprint文を1つ追加してください。

``` python
__pyarmor__(__name__, __file__, b'...', 1)
print('This is obfuscated module')
```

このスクリプトはインポート時に `restrict exception` を発生させます。

+ モード2
このモードでは、難読化されたスクリプトはプレーン・スクリプトからインポートすることができず、メイン・スクリプトは [エントリースクリプト](#エントリースクリプト) として難読化されなければなりません。このスクリプトはPythonインタープリタから直接実行されるか、他の難読化されたスクリプトからインポートされる可能性があります。インポートされるとき、呼び出し元とメインスクリプトをチェックし、両方が難読化されていることを確認します。

例えば、foo2.pyはモード2によって難読化されています。このように実行することができます。

``` python
python foo2.py
```

しかし、任意のプレーンスクリプトからそれをインポートしてみてください。たとえば、

``` python
python -c'import foo2'
```

protection exceptionが発生します。

+ モード3
モード2の強化版で、モジュールの属性も保護します。モジュール属性にアクセスするとき、あるいはモジュール関数を呼び出すとき、呼び出し元をチェックし、呼び出し元が難読化されていない場合は保護例外を発生させます。

+ モード4
モード3とほぼ同じですが、唯一の違いは、インポート時にメインスクリプトが難読化されているかどうかをチェックしないことです。

主にPythonのパッケージを難読化するのに使われます。一般的な方法は、 `__init__.py` を restrict mode 1 で難読化し、このパッケージの他のすべてのモジュールを restrict mode 4 で難読化する、というものです。

例えば、`mypkg` というパッケージがあるとします。

``` python
mypkg/
    __init__.py
    private_a.py
    private_b.py
```

`__init__.py` で、プレーンスクリプトが使用するパブリック関数とアトリビュートを定義します。

``` python 
from . import private_a as ma
from . import private_b as mb

public_data = 'welcome'

def proxy_hello():
    print('Call private hello')
    ma.hello()

def public_hello():
    print('This is public hello')
```

`private_a.py` で、プライベート関数とアトリビュートを定義します。

``` python
import sys

password = 'xxxxxx'

def hello():
    print('password is: %s' % password)
```

そして、`__init__.py` を モード1 で、その他を モード4 で難読化し、 `dist` に格納してください。

```
dist/
    __init__.py
    private_a.py
    private_b.py
```
では、Pythonインタプリタからいくつかテストしてみましょう。

``` python
import dist as mypkg

# It works
mypkg.public_hello()
mypkg.proxy_hello()
print(mypkg.public_data)
print(mypkg.ma)

# It doesn't work
mypkg.ma.hello()
print(mypkg.ma.password)
```

+ モード5（v6.4.0での新機能）
モード 5 は、モード 4 の強化版で、フレーム内のグローバルも保護します。モード5で関数を実行すると、外側のプレーン・スクリプトはこの関数のグローバルから何も得ることができません。このモードは、スタンドアロン・スクリプトとパッケージの両方で使用できる最高のセキュリティです。ただし、実行時に各グローバル変数をチェックするため、パフォーマンスが低下する可能性があります。

+ モード100+ (v6.7.4での新機能)
このモードは、モード1〜5を拡張し、モジュール属性 `__dict__` の制限を有効にしたものです。つまり、モード101はモード1にこの機能を加えたもの、モード102はモード2にこの機能を加えたものに相当します。

この機能を有効にすると、モジュール属性 `__dict__` は空の辞書のように見えます。そして、このモジュールのために変更されたことがあります。

+ `module.__dict__` にあるすべてのオブジェクトは、このモジュールのクリーンアップ時にクリーンアップされません。
+ このモジュールがインポートされた後、`module.__dict__` は、暗黙的にも明示的にも、項目の挿入や削除ができなくなります。
+ `dirs`, `vars` の関数も空文字を返します。

たとえば、

``` python
# This is foo6.py, obfuscated with mode 105

# It's OK
global var_a
var_a = 'This is global variable a'
var_b = 'This is global variable b'
del var_a

def fabico():
    global var_b

    # Wrong, remove item from module.__dict__ not in modul level
    del var_b

# This is foo.py, obfuscated with mode 101

import foo6

# The result is {}
foo6.__dict__

# The output is {}
vars(foo6)

# The output is []
dirs(foo6)

# OK
foo6.var_a = 'Changed by foo'

# Wrong, add new item to __dict__
foo6.__dict__['var_c'] = 1
foo6.var_d = 2
```

この機能はPython 3.7以降で動作し、それ以前のバージョンのPythonでは効果がありません。

:::note warn
モード3、4のモジュール属性の保護はv6.3.7で導入されました。それ以前は、関数の呼び出しのみが保護されます。

モジュール属性のみが保護されるため、public `__init__.py` で `private` モジュールから関数やクラスをインポートしないでください。

``` python
# Right, import module only
from . import private_a as ma

# Wrong, function `hello` is opened for plain script
from .private_a import hello
```
:::

:::note info
モード2と3はPythonパッケージの難読化に使用できません。なぜなら、メインスクリプトも難読化されていなければならず、そうでなければインポートされないからです。
:::

:::note info
Restrict modeは1つのスクリプトに適用され、異なるスクリプトは異なるRestrict modeによって難読化される可能性があります。
:::

:::note info
もし、 `--obf-code=0` で難読化されているスクリプトがあれば、それはプレーンなスクリプトとして扱われます。

例えば、1つのパッケージに3つのスクリプトがあるとします。

1. `__init__.py` : [ restrict_mode : 1, obf-code 2 ]
2. `foo.py` : [restrict_mode : 4, obf-code 2] 
3. `bar.py` : [restrict_mode : 1, obf-code 0] 

ここで、`bar.py` は `obf-code=0` のため、実行時にプレーンなスクリプトとして表示されます。

したがって、`foo.py` は、プレーンスクリプトのように見えるので、 `bar.py` の内部でインポートすることができません。しかし、`foo.py` は `__init__.py` の中で `obf-code=2` を持っているので、インポートすることができます。
:::

PyArmor 5.2からは、Restrict Mode 1がデフォルトになりました。

他のrestrict modeによるスクリプトの難読化、

``` python
pyarmor obfuscate --restrict=2 foo.py
pyarmor obfuscate --restrict=4 foo.py

# For project
pyarmor config --restrict=2
pyarmor build -B
```

必要であれば、この方法で上記の制限をすべて無効にすることができます。

``` python
pyarmor obfuscate --restrict=0 foo.py

# For project
pyarmor config --restrict=0
pyarmor build -B
```

スクリプトが [licenses](https://pyarmor.readthedocs.io/en/latest/man.html#licenses) によって生成されたライセンスを使用する場合、すべての制限を無効にするには、[licenses](https://pyarmor.readthedocs.io/en/latest/man.html#licenses) コマンドにオプション `--disable-restrict-mode` を渡します。例えば、以下のようになります。

``` python
pyarmor licenses --disable-restrict-mode r001

pyarmor obfuscate --with-license=licenses/r001/license.lic foo.py

# For project
pyarmor config --with-license=licenses/r001/license.lic
pyarmor build -B
```

より詳細な例については、 [制限モードによるセキュリティの向上](#制限モードによるセキュリティの向上) を参照してください。

PyArmor 5.7.0から、難読化されたスクリプトに別の暗黙の制限があります。 [ブートストラップコード](#ブートストラップコード) は、難読化されたスクリプトに含まれている必要があり、エントリスクリプトとして指定されている必要があります。。例えば、同じフォルダに `foo.py` と `test.py` という2つのスクリプトがあり、このコマンドで難読化されるとします。

``` python
pyarmor obfuscate foo.py
```

難読化されたスクリプト `dist/test.py` にブートストラップコードを手動で挿入しても、エントリスクリプトとして指定されていないため、うまくいきません。 [ブートストラップコード](#ブートストラップコード) を挿入するためには、このコマンドを実行する必要があります。

``` python
pyarmor obfuscate --no-runtime --exact test.py
```

もし、 [ブートストラップコード](#ブートストラップコード) をプレーン・スクリプトに挿入する必要がある場合は、まず、次のように空のスクリプトを難読化します。

``` python
echo "" > pytransform_bootstrap.py
pyarmor obfuscate --no-runtime --exact pytransform_bootstrap.py
```

そして、プレーンスクリプトでpytransform_bootstrapをインポートしてください。

# 14.難読化されたスクリプトのパフォーマンス

難読化されたスクリプトのパフォーマンスを確認するために、[benchmark](https://pyarmor.readthedocs.io/en/latest/man.html#benchmark) コマンドを実行します。

``` python
pyarmor benchmark
```

以下は出力例です。

``` python
INFO     PyArmor Trial Version 6.3.0
INFO     Python version: 3.7
INFO     Start benchmark test ...
INFO     Obfuscate module mode: 1
INFO     Obfuscate code mode: 1
INFO     Obfuscate wrap mode: 1
INFO     Obfuscate advanced mode: 0
INFO     Benchmark bootstrap ...
INFO     Benchmark bootstrap OK.
INFO     Run benchmark test ...

Test script: bfoo.py
Obfuscated script: obfoo.py
--------------------------------------

import_first_no_obfuscated_module                 :   6.177000 ms
import_first_obfuscated_module                    :  15.107000 ms

re_import_no_obfuscated_module                    :   0.004000 ms
re_import_obfuscated_module                       :   0.005000 ms

--- Import 10 modules ---
import_many_no_obfuscated_modules                 :  58.882000 ms
import_many_obfuscated_modules                    :  50.592000 ms

run_empty_no_obfuscated_code_object               :   0.004000 ms
run_empty_obfuscated_code_object                  :   0.003000 ms

run_no_obfuscated_1k_bytecode                     :   0.010000 ms
run_obfuscated_1k_bytecode                        :   0.027000 ms

run_no_obfuscated_10k_bytecode                    :   0.053000 ms
run_obfuscated_10k_bytecode                       :   0.119000 ms

call_1000_no_obfuscated_1k_bytecode               :   2.411000 ms
call_1000_obfuscated_1k_bytecode                  :   3.735000 ms

call_1000_no_obfuscated_10k_bytecode              :  32.067000 ms
call_1000_obfuscated_10k_bytecode                 :  42.164000 ms

call_10000_no_obfuscated_1k_bytecode              :  22.387000 ms
call_10000_obfuscated_1k_bytecode                 :  36.666000 ms

call_10000_no_obfuscated_10k_bytecode             : 307.478000 ms
call_10000_obfuscated_10k_bytecode                : 407.585000 ms

--------------------------------------
INFO     Remove test path: ./.benchtest
INFO     Finish benchmark test.
```

2つの関数を含む単純なスクリプトbfoo.pyを使用します。

bfoo.pyというシンプルなスクリプトを使い、以下の2つの関数を含んでいます。

+ one_thousand: バイトコードの大きさは約1kです
+ ten_thousand: 10k程度のバイトコード

`import_first_obfuscated_module` の処理時間には、ダイナミックライブラリの初期化時間やライセンスチェックの時間などが含まれており、通常のスクリプトよりも多くの時間を消費しています。
ただし、スクリプトを約10個の新しいファイルにコピーして新しい名前でインポートする`import_man y_obfuscated_modules`は、通常のスクリプトよりも早く、難読化されたスクリプトがコンパイルされているため、コンパイル時間が節約されます。

残りのテスト、例えば `call_1000_no_obfuscated_1k_bytecode` は、関数 `one_thousand` を1000回呼び出すことを表しています。`call_1000_obfuscated_1k_bytecode` の結果を比較し、難読化スクリプトの性能を知ることができます。結果はテストスクリプト、Pythonのバージョン、難読化モードなどに依存することに注意してください。

利用可能なオプションをすべてリストアップします。

``` python
pyarmor benchmark -h
```

他のオプションを指定して、異なるモードでのパフォーマンスを確認することができます。たとえば、

``` python
pyarmor benchmark --wrap-mode 0 --obf-code 2
```

ベンチマークテストの実行に使用されるスクリプトを見ます。

``` python
pyarmor benchmark --debug
```

使用したファイルはすべて `.benchtest` フォルダに保存されます。

## 異なるモードでの性能
`obf-mod` では、2の方がより安全で高速なので、v6.3.0で導入されて以来、デフォルト値となっています。
`obf-code` では、2は1より安全性が高く、1より若干遅いです。

`wrap-mode` は、0だと各関数が1回ずつ復元されますが、1だと呼び出した数だけ復元されます。このため、特に何度も呼び出される関数の場合は、後者の方が遅くなります。

[アドバンストモード](#advanced-mode) と [スーパーモード](#super-mode) は、ほぼ同じ性能です。しかし、 [VMモード](#vm-mode) はコア機能が仮想化されているため、パフォーマンスが低下します。

## 難読化したスクリプトでcProfileを実行する
難読化されたスクリプトは、例えばcProfileやprofileで動作させることができます。たとえば、

``` python
pyarmor obfuscate foo.py

python -m cProfile dist/foo.py
```

難読化されたスクリプトの中には，cProfileやprofileの下で例外を発生させるものがあります．
`cProfile.py` や `profile.py` のソーススクリプトにパッチを当ててください。

:::note info
古いバージョンの pyarmor では cProfile や profile が動作しないことがあります。この場合は pyarmor をアップグレードして、再度スクリプトを難読化することを試してください。
:::

## ビッグスクリプトの性能
MacOS 10.14, 8G RAM, Python 3.7でビッグスクリプトをテストする例です。

テストスクリプト `big.py` のサイズは約81Mで、異なる名前で多くの同じ関数が定義されています。

``` python
def fib0(n):   # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return result


def fib1(n):   # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return result


def fib2(n):   # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return result

...
```

メインスクリプト `main.py`

``` python
import sys
import time

def metricmethod(func):
    if not hasattr(time, 'process_time'):
        time.process_time = time.clock

    def wrap(*args, **kwargs):
        t1 = time.process_time()
        result = func(*args, **kwargs)
        t2 = time.process_time()
        print('%-50s: %10.6f ms' % (func.__name__, (t2 - t1) * 1000))
        return result
    return wrap

@metricmethod
def import_big_module(name):
    return __import__(name)

@metricmethod
def call_module_function(m):
    m.fib2(20)

name = sys.argv[1] if len(sys.argv) > 1 else 'big'
call_module_function(import_big_module(name))
```

さまざまなケースで `python3main.py` を実行します

__pycache__なし、難読化なし、

``` python
import_big_module      : 52905.399000 ms
call_module_function   :   0.020000 ms
```

`__pycache__` があれば、とても速いです。

``` python
import_big_module      : 2065.303000 ms
call_module_function   :   0.011000 ms
```

次に大きなスクリプトを難読化します。

``` python
pyarmor obfuscate big.py
```

そして、そのテスト結果、

``` python
import_big_module      : 8690.256000 ms
call_module_function   :   0.015000 ms
```

# 15.PyArmorのセキュリティ
PyArmorは、Pythonモジュールを2つのレベルで難読化します。まず、モジュール内の各関数を難読化し、次にモジュールファイル全体を難読化します。例えば、foo.pyというファイルがあるとします。

``` python
def hello():
    print('Hello world!')

def sum(a, b):
    return a + b

if __name == '__main__':
    hello()
    print('1 + 1 = %d' % sum(1, 1))
```

PyArmorはまず関数 `hello` と `sum` を難読化し、次に `foo モジュール全体を難読化します。実行時には、現在呼び出されている関数だけが復元され、コード・オブジェクトの実行が完了すると同時に難読化されます。従って、任意のC言語デバッガでコードをトレースしても、一度に得られるのはコード・オブジェクトの一部分だけです。

## pytransformのためのクロスプロテクション

PyArmorのコアとなる関数は、動的ライブラリ `_pytransform` の中で `c` によって書かれています。`_pytransform`はJITテクニカルによって自身を保護し、難読化されたスクリプトは `_pytransform`によって保護されます。一方、難読化されたスクリプトでは、動的ライブラリ `_pytransform` が変更されていないことを確認します。これは Cross Protection と呼ばれます。

動的ライブラリ `_pytransform.so` はJITテクニカルを使用して、2つのタスクを実現しています。

+ python スクリプトを暗号化するのに使われる `des` キーを、あらゆる `c` デバッガがトレースできないようにします。
+ コードセグメントはそれ以上変更できないようにします。例えば、`JZ` 命令を `JNZ` に変更し、ライセンスのチェックに失敗しても `_pytransform.so` が実行できるようにします。

JITはどのように動作するのですか？

まず、PyArmorはGNU lightningに基づいた命令セットを定義します。

そして、この命令セットを使って、以下のようなコア関数をcファイルに記述します。

``` python
t_instruction protect_set_key_iv = {
    // function 1
    0x80001,
    0x50020,
    ...

    // function 2
    0x80001,
    0xA0F80,
    ...
}

t_instruction protect_decrypt_buffer = {
    // function 1
    0x80021,
    0x52029,
    ...

    // function 2
    0x80001,
    0xC0901,
    ...
}
```

`_pytransform.so` をビルドし、 `_pytransform.so` のコードセグメントのcodesumを計算する。

関連する命令を前に取得した実際のコードサムに置き換え、cファイルの "function 1 "以外のすべての命令を難読化します。更新されたファイルは次のようなものです。

``` python
t_instruction protect_set_key_iv = {
    // plain function 1
    0x80001,
    0x50020,
    ...

    // obfuscated function 2
    0xXXXXX,
    0xXXXXX,
    ...
}

t_instruction protect_decrypt_buffer = {
    // plain function 1
    0x80021,
    0x52029,
    ...

    // obfuscated function 2
    0xXXXXX,
    0xXXXXX,
    ...
}
```

最後に、この変更されたcファイルを使って `_pytransform.so` をビルドしてください。

難読化されたスクリプトを実行すると、_pytransform.so がロードされます。保護された関数が呼び出されると、次のようになります。

1. 関数1からコードを生成します
2. 関数1を実行します
　　　　　　・コードセグメントのコードサムをチェックし、期待はずれであれば終了する
　　　　　　・tickcountをチェックし、長すぎる場合は、終了します
　　　　　　・デバッガがあるかチェックし、見つかったら終了する
　　　　　　・可能であればハードウェアブレークポイントをクリア
　　　　　　・次の関数である関数2を復元する
3. 関数2からコードを生成します
4. 関数2を実行し、関数1と同じことをします
 
何回か繰り返した後、本当のコードが呼び出されます。これらはすべて、保護コードにブレークポイントがないことを確認するためです。

Pythonスクリプトで_pytransformを保護するために、いくつかの追加コードがエントリスクリプトに挿入されます。 [エントリースクリプトの特殊な取り扱いについて](#エントリースクリプトの特殊な取り扱いについて) を参照してください。

## 異なる機能番号のセキュリティ

各プラットフォームには、異なる機能を持つ複数のダイナミックライブラリが存在する場合があります。プラットフォーム名に機能番号のサフィックスを付けて一意な名前にします。例えば、linux.x86_64.21は機能番号21、windows.x86.0は機能番号0となります。

feature 21とfeature 25のライブラリは、強力なvmツールと多くのアンチデバッグ技術によって保護されており、安全です。

Feature 0は何も保護されていないので、第三者のツールで保護するのがよいでしょう。

その他の機能については、単純なvmといくつかのアンチデバッグ技術で保護されていますが、十分な強度ではないので、サードツールで保護することをお勧めします。

## コア・アルゴリズムの変更
PyArmorは時々コアアルゴリズムを変更することがあり、新しいバージョンで難読化されたスクリプトは以前のものと全く異なる可能性があります。

# 16.うまくいかないとき

pyarmorを使用するには、いくつかの必要な知識や技術が必要です。このリストを確認し、あなたがそれらを知っていること、そしてあなたの質問がそれらに関連するものでないことを確認してください。

## 必要な知識

### Shell

pyarmorはコマンドラインツールなので、シェルまたはターミナルで実行する必要があります。シェルコマンドの知識がない場合は、代わりに [pyarmor-webui](https://pypi.org/project/pyarmor-webui/) を使用してください。

pyarmorがコマンドを実行すると、引数エラーや不明なオプションがあると文句を言われます。 `-h` オプションで利用可能なオプションをすべてリストアップし、これらのヒントでコマンドの構文エラーを修正してください。たとえば、

``` python
pyarmor obfuscate -h
```

### Python

Pythonの実行方法 [https://docs.python.org/3.8/tutorial/interpreter.html#using-the-python-interpreter](https://docs.python.org/3.8/tutorial/interpreter.html#using-the-python-interpreter)

### Source Code Encoding

難読化されたスクリプトが予期せぬ出力をする場合、次のことを学ぶ必要があります。

[https://docs.python.org/3.8/tutorial/interpreter.html#source-code-encoding](https://docs.python.org/3.8/tutorial/interpreter.html#source-code-encoding)

次に、スクリプトに正しいソースコードエンコーディングを設定し、最初にプレーンスクリプトを実行してすべてがうまくいっていることを確認し、それからスクリプトを再度難読化してください。

### Python Import System

難読化されたスクリプトを実行するには、追加の [ランタイムパッケージ](#ランタイムパッケージ) が必要です。これは一般的なPythonパッケージであり、通常のPythonモジュールやパッケージとしてインポートすることができます。このパッケージが正しくインポートされない場合、例えば難読化スクリプトと一緒に配布されていなかったり、間違った場所に格納されていたりすると、難読化スクリプトは以下のような例外を発生させる可能性があります。

``` python
ModuleNotFoundError: No module named 'app.pytransform'
```

これはPyArmorのエラーではなく、Pythonがそれを見つけられないだけです。この場合、Pythonのモジュールやパッケージのインポート方法、絶対インポートと相対インポート、 `sys.path` が何であるかを知っておく必要があります。

[https://docs.python.org/3.8/library/sys.html#sys.path](https://docs.python.org/3.8/library/sys.html#sys.path)

難読化されたスクリプトは非常にシンプルなPythonスクリプトで、最初の行はimport文、2行目は関数呼び出しとなっています。importやno module foundのエラーが発生した場合、たとえば、

``` python
ImportError: No module named model.NukepediaDB
```

一般的なPythonスクリプトと同じように考えて、Python Import Systemに従ってモジュール、パッケージ、または拡張ファイルが正しい場所にあるかどうかをチェックします。そうでない場合は、モジュール、パッケージ、拡張機能ファイルを正しいパスに移動してください。

PythonのImport Systemについては、以下の公式ドキュメントや検索エンジンを参照してください。
[https://docs.python.org/3.8/reference/simple_stmts.html#the-import-statement](https://docs.python.org/3.8/reference/simple_stmts.html#the-import-statement)

## PyInstaller

難読化されたスクリプトを1つの実行ファイルにまとめたい場合で、そしてプロジェクトの構造が複雑な場合は、 [PyInstaller](https://pyinstaller.org/en/stable/) を知っている必要がありますが、直接 [PyInstaller](https://pyinstaller.org/en/stable/) でプロジェクトをまとめることができます。

[https://pyinstaller.readthedocs.io/en/stable/usage.html](https://pyinstaller.readthedocs.io/en/stable/usage.html)

## 一般的な解決策

私は多くの問題を受け取っています。それらのほとんどはpyarmorの欠陥ではなく、pyarmorを間違った方法で使用しています。 したがって、pyarmorで困ったことがある場合は、pyarmorを理解するために数時間を費やすと、問題がすぐに解決する可能性があります。 自助は他人からの助けよりも優れています、それはまた私たちの両方のための時間を節約することができます。

まず、基本ガイドの [PyArmorの使用](#3pyarmorの使用) を読んでいることを確認してください。

[難読化されたスクリプトを理解する](#9難読化されたスクリプトを理解する)、特に [難読化されたスクリプトの違いについて](#難読化されたスクリプトの違いについて) のセクションに目を通してください。

特殊なケースでpyarmorをどう使えばいいかわからない場合は、 [アドバンストピックス](#4アドバンストピックス)  の 目次に目を通してください。

以下は、いくつかの一般的な解決策です。

+ pyarmorを最新の安定版にアップグレードします。アップグレードする前に [変更履歴](https://pyarmor.readthedocs.io/en/latest/change-logs.html#change-logs) を確認してください。pyarmorが以前はうまく動作していて現在は動作しない場合、 [クリーンアンインストール](#クリーンアンインストール) を行い、pyarmorを再インストールして、リフレッシュした状態からすべてを起動します。

+ pyarmorによるスクリプトの難読化については、最後のエラーメッセージだけでなく、各ログを注意深くチェックして、 `pyarmor` が何をしているかを理解すると、問題を見つけるのに非常に役に立ちます。また、一般的なオプションである `-d` を使って、より多くの情報を取得するようにします。たとえば、

``` python
pyarmor -d obfuscate --recursive foo.py
```

難読化されたスクリプトを実行する際、Pythonのデバッグオプション `-d` をオンにすると、より詳細な情報が表示されます。トレースバックの中に行番号とスクリプト名があれば、その行の周辺のソーススクリプトを確認してください。難読化されたスクリプトによって変更された機能が使われていないことを確認してください。たとえば、

``` python
python -d obf_foo.py
```

+ 難読化したスクリプトを異なるプラットフォームやDockerで配布する場合、関連するクロスプラットフォームオプションが設定されていることを確認してください。難読化されたスクリプトはバイナリライブラリを含むため、プラットフォームに依存し、ターゲットのPythonのバージョンは難読化するスクリプトのバージョンと同じである必要があります。

+ [pack](https://pyarmor.readthedocs.io/en/latest/man.html#pack) コマンドを使用している場合、 `PyInstaller` がプレーンスクリプトを直接パックすることができ、最終的なバンドルが動作することを確認します。

+ [Restrict Mode](#restrict-mode) 3 以上で難読化されたスクリプトを使用している場合、デフォルトの制限モードを使用してみてください。低い制限モードで動作する場合、スクリプトをチェックして、制限モードに違反していないことを確認してください。

+ 複雑なスクリプトやパッケージを使っている場合は、簡単なスクリプトやパッケージで動作確認をしてください。

+ よくわからないことがあれば、数分でテストを行い、pyarmor を理解してください。

pyarmorのデフォルトのオプションは一般的なケースでは有効ですが、複雑なケースでは、各コマンドの異なるオプションを理解する必要があります。まず、 `-h` オプションでobfuscateの利用可能な全オプションをリストアップします。

``` python
pyarmor obfuscate -h
```

簡単な説明で目的のオプションを見つけることができます。 よくわからない場合は、 [Man Page](https://pyarmor.readthedocs.io/en/latest/man.html#man-page) にアクセスして各オプションの詳細を確認してください。
オプションを理解する一番簡単な方法は、1分間でテストすることです。例えば、 `-bootstrap` というオプションは、難読化されたスクリプトのブートストラップ・コードの生成方法を制御するために使われます。

``` python
cd /path/to/test
mkdir case-1
cd case-1
echo "print('Hello')" > foo.py
pyarmor obfuscate --bootstrap 2 foo.py
ls dist/
cat dist/foo.py

cd /path/to/test
mkdir case-2
cd case-2
echo "print('Hello')" > foo.py
pyarmor obfuscate --bootstrap 3 foo.py
ls dist/
cat dist/foo.py
```

異なるオプションを組み合わせて同様のテストを行うことができますので、pyarmorの理解を早めることができるかもしれません。

:::note info
多くのレポーティングされた [issue](https://github.com/dashingsoft/pyarmor/issues/) がありますので、まずはこちらで同じ問題を探してみてください。
:::

## 問題を報告する

ドキュメントに解決策がない場合、セキュリティ上の問題については、pyarmor@163.com にメールを送ってください。それ以外の場合は、issue to report をクリックして、必要な情報を提供してください。

1. pyarmorコマンドと出力ログの全文(必須)
2. 難読化されたスクリプトを他のマシンに配布する場合、どのファイルがコピーされるか（任意）
3. 難読化されたスクリプトを実行するコマンドと、何か問題があった場合の完全なトレースバック

出力ログはこの方法でファイルにリダイレクトすることができます。

``` python
pyarmor obfuscate foo.py >log.txt 2>&1
```

ここでは一例として、問題のタイトルを紹介します。

``` python
cannot import name 'pyarmor' from 'pytransform'
```

issueの内容（これらを全てgithubにコピーして修正します）。

``` python
1. On MacOS 10.14 run pyarmor to obfuscate the script
・・・・・
$ pyarmor obfuscate --exact main.py
INFO     Create pyarmor home path: /Users/jondy/.pyarmor
INFO     Create trial license file: /Users/jondy/.pyarmor/license.lic
INFO     Generating public capsule ...
INFO     PyArmor Trial Version 7.0.1
INFO     Python 3.7.10
INFO     Target platforms: Native
INFO     Source path is "/Users/jondy/workspace/pyarmor-webui/test/__runner__/__src__"
INFO     Entry scripts are ['main.py']
INFO     Use cached capsule /Users/jondy/.pyarmor/.pyarmor_capsule.zip
INFO     Search scripts mode: Exact
INFO     Save obfuscated scripts to "dist"
INFO     Read product key from capsule
INFO     Obfuscate module mode is 2
INFO     Obfuscate code mode is 1
INFO     Wrap mode is 1
INFO     Restrict mode is 1
INFO     Advanced value is 0
INFO     Super mode is False
INFO     Super plus mode is not enabled
INFO     Generating runtime files to dist/pytransform
INFO     Extract pytransform.key
INFO     Generate default license file
INFO     Update capsule to add default license file
INFO     Copying /Users/jondy/workspace/pyarmor-webui/venv/lib/python3.7/site-packages/pyarmor/platforms/darwin/x86_64/_pytransform.dylib
INFO     Patch library dist/pytransform/_pytransform.dylib
INFO     Patch library file OK
INFO     Copying /Users/jondy/workspace/pyarmor-webui/venv/lib/python3.7/site-packages/pyarmor/pytransform.py
INFO     Rename it to pytransform/__init__.py
INFO     Generate runtime files OK
INFO     Start obfuscating the scripts...
INFO        /Users/jondy/workspace/pyarmor-webui/test/__runner__/__src__/main.py -> dist/main.py
INFO     Insert bootstrap code to entry script dist/foo.py
INFO     Obfuscate 1 scripts OK.
・・・・・
2. Copy the whole folder `dist/` to target machine Ubuntu
3. Failed to run the obfuscated script by Python 3.7 in Unbutu
・・・・・
$ cd dist/
$ python3 main.py
Traceback (most recent call last):
File "main.py", line 1, in <module>
  from pytransform import pyarmor
ImportError: cannot import name 'pyarmor' from 'pytransform' (/home/jondy/dist/pytransform/__init__.py)

```

:::note info
問題は無効としてマークされ、次のいずれかで直接クローズされる可能性があります。
+ テンプレートとして報告されていない、または必要な情報がない
+ ドキュメントに正確な解決策がある場合
:::

## Segment fault

以下のような場合、難読化されたスクリプトがクラッシュすることがあります。

+ デバッグ版Pythonで難読化したスクリプトを実行した場合
+ Python X.Yで難読化したが、別のPythonバージョンM.Nで難読化したスクリプトを実行した場合
+ 異なるプラットフォームでスクリプトを実行するが、 `--platform` オプションを指定せずに難読化する
　　・DockerではAlpine Linux、PyArmorではlinux.x86_64ではなくmusl.x86_64というプラットフォーム名で実行されます
　　・Windowsでは、32ビットWindowsと64ビットWindowsは別物です
　　・64ビットWindowsでは、32ビットPythonと64ビットPythonは異なります
+ 難読化されたコードオブジェクトの `co_code` やその他の属性を何らかの方法で読み取る、一部のサードパッケージはバイトコードを解析して何かを行う可能性があります
+ 制限モード3以上で難読化されたスクリプトを難読化されていないスクリプトで読み込むとクラッシュすることがあります。また、`obf-code=0` で難読化されたスクリプトもクラッシュする可能性があります
+ 異なるオプション `--advanced` で難読化されたスクリプトを混在させる
+ MacOSでは、pyarmorのコアライブラリは標準システムのPythonにリンクされていますが、その他のマシンでは、`install_name_tool` を使って `rpath` を変更し、このマシンに適応させます。

PyArmor 5.5.0 ~ 6.6.0 では、advanced mode が原因で一部のマシンがクラッシュすることがあります。簡単な対処法は、`pytransform.py`（pyarmorのインストール先）を編集してアドバンストモードを無効にし、`_load_library` 関数で202行目をアンコメントすることです。最終的には以下のようなコードになります。

``` python
# Disable advanced mode if required
m.set_option(5, c_char_p(1))
```

## Bootstrapの問題

### pytransformが見つからない

一般的に、ダイナミックライブラリ `_pytransform` は [ランタイムパッケージ](#ランタイムパッケージ) にあり、v5.7.0以前では難読化されたスクリプトと同じパスにあります。おそらく。

+ Linuxでは_pytransform.so
+ Windowsでは、_pytransform.dllです。
+ MacOSの_pytransform.dylib

まず、そのファイルが存在するかどうかを確認します。存在する場合は、

+ ダイナミックライブラリの実行権限を確認する
　　　　Windowsで実行権限がない場合、文句を言われます。[エラー5]アクセスが拒否されました

+ `ctypes` が `_pytransform` を読み込むことができるかどうかを確認します

``` python
from pytransform import _load_library
m = _load_library(path='/path/to/dist')
```

+ エントリースクリプトの [ブートストラップコード](#ブートストラップコード) にランタイムパスを設定してみてください

``` python
from pytransform import pyarmor_runtime
pyarmor_runtime('/path/to/dist')
```

それでも動作しない場合は、[問題](https://github.com/dashingsoft/pyarmor/issues/) を報告してください

### ERROR: Unsupport platform linux.xxx

[サポートプラットフォーム](https://pyarmor.readthedocs.io/en/latest/platforms.html#support-platforms) をご覧ください。

### /lib64/libc.so.6: version ‘GLIBC_2.14’ not found

GLIBC_2.14が存在しないマシンでは、この例外が発生します。

解決策の一つは、以下の方法で_pytransform.soにパッチを適用することです。

まず、バージョン情報を確認します。

``` python
readelf -V /path/to/_pytransform.so
...

Version needs section '.gnu.version_r' contains 2 entries:
 Addr: 0x00000000000056e8  Offset: 0x0056e8  Link: 4 (.dynstr)
  000000: Version: 1  File: libdl.so.2  Cnt: 1
  0x0010:   Name: GLIBC_2.2.5  Flags: none  Version: 7
  0x0020: Version: 1  File: libc.so.6  Cnt: 6
  0x0030:   Name: GLIBC_2.7  Flags: none  Version: 8
  0x0040:   Name: GLIBC_2.14  Flags: none Version: 6
  0x0050:   Name: GLIBC_2.4  Flags: none  Version: 5
  0x0060:   Name: GLIBC_2.3.4  Flags: none  Version: 4
  0x0070:   Name: GLIBC_2.2.5  Flags: none  Version: 3
  0x0080:   Name: GLIBC_2.3  Flags: none  Version: 2
```

そして、GLIBC_2.14のエントリーをGLIBC_2.2.5に置き換えます。

+ 0x56e8+0x10=0x56f8にある4バイトを0x56e8+0x40=0x5728にコピーします
+ 0x56e8+0x18=0x5700の4バイトを0x56e8+0x48=0x5730にコピーします

以下はコマンドの例です。

``` python
xxd -s 0x56f8 -l 4 _pytransform.so | sed "s/56f8/5728/" | xxd -r - _pytransform.so
xxd -s 0x5700 -l 4 _pytransform.so | sed "s/5700/5730/" | xxd -r - _pytransform.so
```

:::note info v5.7.9からは、このパッチは必要ありません。クロスプラットフォームでは、プラットフォームを centos6.x86_64 に指定するだけで、この問題を解決できます。たとえば、

``` python
pyarmor obfuscate --platform centos6.x86_64 foo.py
```
:::

### ‘pyarmor’ is not recognized issue

pyarmorがpipでインストールされている場合、コンピュータ内で "pyarmor"を検索し、フルパスで `pyarmor` を実行するか、環境変数PATH にpyarmorのパスを追加してください。

pipでインストールされていない場合、pyarmorコマンドに相当するのは、配布フォルダーにあるPythonスクリプト "pyarmor.py"を実行することです。

### __snprintf_chk: symbol not found

pyarmor を一部の dockers で実行すると、この例外が発生することがあります。これらのドッカーは `musl-libc` でビルドされていますが、デフォルトの `_pytransform.so` は `glibc` でビルドされているため、`__snprintf_chk` が `musl-libc` で欠落しているのです。

この場合、対応するダイナミックライブラリをダウンロードしてみてください。

``` python
# For x86_64
pyarmor download musl.x86_64.7

# For arm64
pyarmor download musl.aarch64.3

# For armv7l
pyarmor download musl.arm.0
```

そして、トレースバックでファイル名が確認できた古いものを上書きします。

pyarmor v6.7.0以前は、以下の方法で最新版をダウンロードしてください。

x86/64版 [http://pyarmor.dashingsoft.com/downloads/latest/alpine/_pytransform.so](http://pyarmor.dashingsoft.com/downloads/latest/alpine/_pytransform.so)

ARM用 [http://pyarmor.dashingsoft.com/downloads/latest/alpine.arm/_pytransform.so](http://pyarmor.dashingsoft.com/downloads/latest/alpine.arm/_pytransform.so)

### Apple M1 Hangs Issue

Apple M1 with [Features](https://pyarmor.readthedocs.io/en/latest/platforms.html#features) 3でpyarmorを実行すると、ハングアップすることがあります。pyarmorのコアライブラリ `_pytransform.dylib` で `JIT-compile`を使用しているためです。

Apple M1ではブロックされてしまうので、対応するエンタイトルメントで署名すると解決する場合があります。[https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-jit](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-jit) を参照してください。

## スクリプトの難読化問題

### Warning: code object xxxx isn’t wrapped

これは、いくつかの特別な指示が含まれているため、この関数が難読化されていないことを意味します。

たとえば、2バイトの命令JMP 255があり、コードオブジェクトが難読化された後、オペランドが267に増加し、命令は次のように変更されます。

``` python
EXTEND 1
JMP 11
```

この場合、ラップモードでコードオブジェクトを難読化するのは複雑です。 したがって、コードオブジェクトは非ラップモードで難読化されますが、他のすべてのコードオブジェクトは依然としてラップモードで難読化されます。

現在のバージョンでは、この関数に未使用のコードを追加して、オペランドが重要な値にならないようにして、この警告を回避することができます。

:::note info
v5.5.0以前では、この場合、コードオブジェクトはそのままになります
:::

### Code object could not be obufscated with advanced mode 2

なぜなら、この関数には処理できないジャンプ命令が含まれているからです。この場合、この関数を改良して、最初のステートメントがジャンプ命令を生成しないようにすればよいのです。例えば、代入、関数呼び出しなどの単純な文です。
しかし、複合文、例えばtry、for、if、with、whileなどはジャンプ命令を発生させます。どうしてもリファクタリングができない場合は、この関数の先頭に次のようなステートメントを挿入します。

``` python
[None, None]
```

いくつかの命令が生成されますが、何も変わりません。

### Error: Try to run unauthorized function

現在のパスに `license.lic` や `pytransform.key` があると、Pyarmorはこのエラーを報告するかもしれません。解決策の一つは、これらのファイルをすべて削除することで、もう一つの解決策は、PyArmorをv5.4.5以降にアップグレードすることです。

### ‘XXX’ codec can’t decode byte 0xXX

スクリプトの最初に正確なソースエンコードを追加してください。たとえば、

``` python
# -*- coding: utf-8 -*-
```

[https://docs.python.org/2.7/tutorial/interpreter.html#source-code-encoding](https://docs.python.org/2.7/tutorial/interpreter.html#source-code-encoding) を参照してください。

ソースコードがメインスクリプトに追加されている場合でも、この問題が発生します。出力ログをチェックして、正確なスクリプト名を確認してください。

### Why plugin doesn’t work

プラグインスクリプトが期待通りに動作しない場合は、まずPythonのデバッグフラグを設定して、プラグインスクリプトがエントリスクリプトに注入されている可能性があることを確認してください。

``` python
# In linux
export PYTHONDEBUG=y
# In Windows
set PYTHONDEBUG=y

pyarmor obfuscate --exact --plugin check_ntp_time foo.py
```

パッチファイル `foo.py.pyarmor-patched` が生成され、プラグインスクリプトの内容が正しい場所に挿入されていることが確認され、そしてverify関数が実行されます。

## 難読化されたスクリプトを実行する問題

### The license.lic generated doesn’t work

重要なのは、スクリプトを難読化するために使用するカプセルと、ライセンスを生成するために使用するカプセルは同じでなければならないということです。

PyArmorの試用版ライセンスファイルを通常のものに置き換えたり、たまに削除したりすると、 [グローバルカプセル](#グローバルカプセル) は変更されます（次回pyarmor obfuscateコマンドを実行すると、暗黙のうちに生成されます）。

いずれの場合も、別のカプセルで新しいライセンスファイルを生成すると、以前の難読化スクリプトでは動作しません。古いカプセルがなくなった場合、新しいカプセルでこれらのスクリプトを再度難読化することが1つの解決策です。

### NameError: name `__pyarmor__` is not defined

難読化されたスクリプトをインポートする前に、 [ブートストラップコード](#ブートストラップコード) が実行されていません。

+ mod サブプロセスやマルチプロセッシングで Popen や Process によって新しいプロセスを作るとき、サブプロセスで難読化されたコードをインポートする前に [ブートストラップコード](#ブートストラップコード) が呼ばれることを確認します。さもなければ、この例外が発生します。
+ `pytransform.py` または `pytransform/__init__.py`でこの例外が発生した場合、難読化されていないことを確認してください。プレーンスクリプトである必要があります。
+ また、システムモジュールの `os`, `ctypes` をチェックして、それらが難読化されていないことを確認してください。オプション `--exclude` を使って、Pythonシステムライブラリのパス全体を除外してみてください。
+ 
[ブートストラップコード](#ブートストラップコード) が実行されたかどうかを確認する方法は？ 一つの簡単な方法は、それらの前に一つのprint文を挿入することです。たとえば、

``` python
print('Start to run bootstrap code')
from pytransfrom import pyarmor_runtime
pyarmor_runtime()
```

メッセージが印刷されればOKです。printステートメントを削除して、他の原因を確認してください。

この問題の他の解決策は、[スーパーモードを使用して](#スーパーモードの使用) スクリプトを難読化することです。

### Marshal loads failed when running xxx.py

1. 難読化されたスクリプトを実行するためのPythonのバージョンと難読化するためのPythonのバージョンが同じかどうか確認する。
2. 難読化されたスクリプトを `python -d` で実行すると、より多くのエラーメッセージが表示されます。
3. ライセンスファイルの生成に使用したカプセルが、スクリプトの難読化に使用したカプセルと同じであることを確認してください。カプセルのファイル名は、コマンド実行時にコンソールに表示されます。
4. クロスプラットフォームの難読化では、ダイナミックライブラリ機能が正しく設定されていることを確認してください。

### _pytransform can not be loaded twice

関数 `pyarmor_runtime` が 2 回呼び出されると、_pytransform can't be loaded twice と表示されます。

例えば、難読化されたモジュールが以下の行を含むとします。

``` python
from pytransform import pyarmor_runtime
pyarmor_runtime()
__pyarmor__(....)
```

エントリースクリプトからこのモジュールをインポートすると、このエラーが報告されます。最初の2行はエントリースクリプトにのみ記述し、他のモジュールには記述してはいけません。

この制限はv5.1から導入されました。このチェックを無効にするには、pytransform.pyを編集して、関数pyarmor_runtimeのこれらの行をコメントするだけです。

``` python
if _pytransform is not None:
    raise PytransformError('_pytransform can not be loaded twice')
```

:::note info
This limitation has been removed from v5.3.5.
:::

### Check restrict mode failed

難読化されたスクリプトを間違った方法で使用した場合、デフォルトではすべての難読化されたスクリプトはそれ以上変更できません。

また、難読化されたスクリプトをパックしても、このエラーは報告されません。難読化されたスクリプトをパックせず、プレーンなスクリプトを直接パックしてください。

詳しくは、 [Restrict Mode](#restrict-mode) を参照してください。

### Protection Fault: unexpected xxx

難読化されたスクリプトを誤って使用すると、デフォルトではすべてのランタイムファイルが変更できなくなります。以下のファイルには触れないでください

+ `pytransform.py`
+ `_pytransform.so/.dll/.dylib`

エントリースクリプトが新しいバージョンで難読化されていても、ランタイムファイルが古いままだと、この例外が発生することがあります。オプション `--no-cross-protection` を使ってこの保護を無効にするか、 オプション `--runtime` を使ってスクリプトを難読化するときに同じランタイムファイルを 指定すると、この問題を解決できるかもしれません。

詳細については、[エントリースクリプトの特殊な取り扱いについて](#エントリースクリプトの特殊な取り扱いについて) を参照してください。

### Run obfuscated scripts reports: Invalid input packet

試用版と購入版を混在させてスクリプトを難読化し、license.licを生成した場合も、この例外が発生する可能性があります。試用版で生成されたすべてのファイル、例えば難読化スクリプト、ライセンスファイル、ランタイムファイルが削除されていることを確認してください。

難読化スクリプトによってインポートされたランタイムモジュールまたはパッケージ pytransform が難読化スクリプトと一緒に配布されたものであることを確認してください。例えば、難読化されたスクリプトpython dist/foo.pyをpyarmorパッケージのソースパスで実行すると、dist/foo.pyによってpyarmorのpytransform.pyが予期せずインポートされてしまい、この例外が発生する可能性があります。

難読化されたスクリプトを別のプラットフォームで使用する場合は、 [難読化したスクリプトを他のプラットフォームへ配布する](#難読化したスクリプトを他のプラットフォームへ配布する) の注意事項を参照してください。

v5.7.0より前のバージョンでは、現在のパスにlicense.licやpytransform.keyがあるかどうか確認してください。難読化されたスクリプトのためにそれらが生成されていることを確認してください。そうでなければ、名前を変えるか、他のパスに移動してください。

なぜなら、難読化スクリプトはまず現在のパスを検索し、次にランタイムモジュールpytransform.pyのパスを検索してlicense.licとpytransform.keyを見つけるからです。もし、難読化されたスクリプトでそれらが生成されない場合、このエラーが報告されます。

### OpenCV fails because of NEON - NOT AVAILABLE

一部のRaspberry Piプラットフォームでは、難読化したスクリプトを実行するとOpenCVのインポートに失敗します。

``` python
************************************************** ****************
* FATAL ERROR: *
* This OpenCV build doesn't support current CPU / HW configuration *
* *
* Use OPENCV_DUMP_CONFIG = 1 environment variable for details *
************************************************** ****************

Required baseline features:
NEON - NOT AVAILABLE
terminate called after throwing an instance of 'cv :: Exception'
  what (): OpenCV (3.4.6) /home/pi/opencv-python/opencv/modules/core/src/system.cpp:538: error:
(-215: Assertion failed) Missing support for required CPU baseline features. Check OpenCV build
configuration and required CPU / HW setup. in function 'initialize'
```

解決策としては、 `optioin --platform` に `linux.armv7.0` を指定することです。

``` python
pyarmor obfuscate --platform linux.armv7.0 foo.py
pyarmor build --platform linux.armv7.0
pyarmor runtime --platform linux.armv7.0
```

もう一つの解決策は、環境変数 `PYARMOR_PLATFORM` に `linux.armv7.0` を設定することです。たとえば、

``` python
PYARMOR_PLATFORM=linux.armv7.0 pyarmor obfuscate foo.py
PYARMOR_PLATFORM=linux.armv7.0 pyarmor build

Or,

export PYARMOR_PLATFORM=linux.armv7.0
pyarmor obfuscate foo.py
pyarmor build
```

### How to customize error message

pyarmorをいじりはじめました。有効期限が切れたライセンスファイルを使用すると、"License is expired "というメッセージが表示されます。このメッセージを変更する方法はありますか？

現時点では、pyarmorパッケージのソーススクリプトpytransform.pyにパッチを当てる必要があります。pyarmor_runtime という関数があります。

``` python
def pyarmor_runtime(path=None, suffix='', advanced=0):
    ...
    try:
        pyarmor_init(path, is_runtime=1, suffix=suffix, advanced=advanced)
        init_runtime()
    except Exception as e:
        if sys.flags.debug or hasattr(sys, '_catch_pyarmor'):
            raise
        sys.stderr.write("%s\n" % str(e))
        sys.exit(1)
```

必要に応じて、例外のハンドラーを変更します。
スクリプトが super mode で難読化されている場合、この解決策はうまくいきません。難読化されたスクリプト `foo.py` が発生する例外をキャッチするスクリプトを作成することができます。たとえば、

``` python
try:
    import foo
except Exception as e:
    print('something is wrong')
```

こうすることで、pyarmorの例外だけでなく、通常のスクリプトの例外も捕捉することができます。pyarmor の例外のみを処理するためには、まず runtime でランタイムパッケージを作成し、それを使ってスクリプトを難読化します。

``` python
pyarmor runtime --advanced 2 -O dist
pyarmor obfuscate --advanced 2 --runtime @dist foo.py
```

そして、次のようなブートスクリプト `dist/foo_boot.py` を作成します。

``` python
try:
    import pytransform_bootstrap
except Exception as e:
    print('something is wrong')
else:
    import foo
```

スクリプトdist/pytransform_bootstrap.pyは実行時に作成され、空のスクリプトから難読化されるため、pyarmorブートストラップ例外のみが発生します。

### undefined symbol: PyUnicodeUCS4_AsUTF8String

Python インタープリタが UCS2 でビルドされている場合、スーパーモード難読化スクリプトを実行すると、この問題が発生することがあります。この場合、UCS2 でビルドされた Centos6.x86_64 プラットフォームで難読化を行ってください。たとえば、

``` python
pyarmor obfuscate --advanced 2 --platform centos6.x86_64 foo.py
```

### NameError: name `__armor_wrap__` is not defined

[Restrictモード](#restrict-mode) が4または5に設定されている場合、この問題が報告されることがあります。この場合、制限モードを 2 に設定してみてください。

オブジェクトメソッド `__del__` で発生した場合、pyarmor を v6.7.3+ にアップグレードし、スクリプトを再度難読化してください。

また、Using restrict mode with threading and multiprocessing と次の質問も参照してください。

### Object method __del__ raise NameError exception

メソッド `__del__` がこの例外を発生させた場合、

``` python
NameError: name '__armor_enter__' is not defined
NameError: name '__armor_wrap__' is not defined
```

pyarmorをv6.7.3+にアップグレードし、スクリプトを再度難読化し、新しいランタイムパッケージが生成されることを確認してください。

もしスクリプトが非スーパーモードで難読化されており、python が 3.7 以降であれば、スクリプトをスーパーモードで難読化してください。あるいは、オブジェクトメソッド `__del__` を難読化しないようにスクリプトを改良してください。たとえば、

``` python
class MyData:

    ...

    def lambda_del(self):
       # Real code for method __del__
       ...

    __del__ = lambda_del
```

`lambda_` で始まる関数名は、pyarmorによって難読化されません。上記の例では、メソッド `lambda_del` は難読化されていないため、 `__del__` は難読化されます。

他の解決策は、 `__del__` を含むスクリプトを難読化せず、難読化されたスクリプトを上書きするために、プレーンなスクリプトをコピーすることです。または、このスクリプトを `--obf-code 0` で難読化する。

### SystemError: module filename missing

スーパーモードでアウターライセンスのスクリプトを難読化する場合、難読化されたスクリプトが現在のパスにライセンスファイル `license.lic` を見つけられなかった場合、このエラーを報告することがあります。

`license.lic` が他のパスにある場合は、環境変数 `PYARMOR_LICNSE` にフルパスで設定します。たとえば、

``` python
export PYARMOR_LICNSE=/path/to/license.lic
```
### Android protection problem

多くのAndroidシステムでは、データパスに動的ライブラリのロードを許可していませんが、難読化したスクリプトのランタイムパッケージには `_pytransform.so` が一つ含まれているため、このような例外が発生する可能性があります。

``` python
dlopen failed: couldn't map "/storage/emulated/0/dist/_pytransform.so"
segment 1: Operation not permitted
```

Androidの開発ドキュメントを参照し、Androidが動的ライブラリの読み込みを許可している場所に `pytransform` フォルダ全体をコピーし、Pythonがそれを見つけてインポートできる場合のみ `PYTHONPATH` やその他の方法で設定してください。

### libpython3.9.so.1.0: cannot open shared object file

python39.dll, libpython3.9.so などの python コアライブラリがない場合、この python インタプリタが `-enable-shared` でビルドされていることを確認してください。デフォルトでは、ランタイム拡張機能のpytransformはpythonのダイナミックライブラリにリンクされています。

Linux の場合、apt などのパッケージ管理ツールで libpython3.9 をインストールしてください。

## 難読化されたスクリプトのパッキングの問題

### The final bundle does not work

まず最初に、 [pack](https://pyarmor.readthedocs.io/en/latest/man.html#pack) の man ページを完全に読んでください。

次に、スクリプトが PyInstaller によって直接パックでき、最終的なバンドルが動作することを確認してください。たとえば、

``` python
pyinstaller foo.py
dist/foo/foo
```

最終的なバンドルでモジュールが見つからないと文句を言われた場合、追加の PyInstaller オプションが必要です。[https://pyinstaller.readthedocs.io](https://pyinstaller.readthedocs.io) を参照してください。

次に、難読化されたスクリプトがパッキングなしで動作することを確認します。たとえば、

``` python
pyarmor obfuscate foo.py
python dist/foo.py
```

両方ともOKなら、出力パスdistとPyInstallerのビルドのキャッシュパスを削除してから、スクリプトを `--debug` でパックします。

``` python
pyarmor pack --debug foo.py
```

ビルドファイルは保持されます。パッチが適用された `foo-patched.spec`をpyinstallerが使用して、難読化されたスクリプトを直接パックできます。

``` python
pyinstaller -y --clean foo-patched.spec
```

このパッチを適用した `.spec` ファイルを確認し、この.specファイルのオプションを変更し、最終的にバンドルが動作することを確認します。

[難読化されたスクリプトでPyInstallerバンドルを再パックする](#難読化されたスクリプトでpyinstallerバンドルを再パックする) を参照してください。この方法で動作することを確認してください。

### No module name pytransform

コマンド pyarmor pack を実行しているときにこのエラーが発生した場合、

+ コマンドラインで指定されたスクリプトが難読化されていないことを確認します
+ キャッシュされた `myscript.spec` を削除するために `--clean` オプションを追加して `pack` を実行してください

``` python
pyarmor pack --clean foo.py
```

### NameError: name ‘__pyarmor__’ is not defined

トレースバックをチェックして、どのスクリプトがこの例外を発生させたかを見つけると、問題を発見するのに役立ちます。

+ `pytransform.py` または `pytransform/__init__.py` がこの例外を発生させた場合、難読化されていないことを確認してください。プレーンなスクリプトである必要があります。
+ システムモジュールの `os` や `ctypes` もチェックして、難読化されていないことを確認してください。この場合、Pythonのシステムライブラリのパスを除外するなどの工夫をしてみてください。

``` python
pyarmor pack -x " --exclude venv" foo.py
```

詳しくは [pack](https://pyarmor.readthedocs.io/en/latest/man.html#pack) を参照してください。

+ 自分のスクリプトだけを空のパスにコピーし、そのパスにパックするようにしてください。
+ 試用版では動作し、pyarmorを登録後に失敗する場合は、 [クリーンアンインストール](#クリーンアンインストール) を試してみてください。

## PyArmorの登録に関する問題

### Purchased pyarmor is not private

購入版で難読化されていても、試用版からのライセンスは機能します。

+ コマンド `pyarmor register` が正しい登録情報を表示していることを確認します
+ [クリーンアンインストール](#クリーンアンインストール) を行い、再度登録します
+ 現在のユーザが、pyarmor を登録するユーザと同じであることを確認します
+ 環境変数 PYARMOR_HOME が設定されていないことを確認します
+ システムを再起動してみてください

### Could not query registration information

コマンドとログを使って、pyarmorに登録しようとしました。

``` python
~ % pyarmor register pyarmor-regfile-1.zip
INFO     PyArmor Version 6.5.2
INFO     Start to register keyfile: pyarmor-regfile-1.zip
INFO     Save registration data to: /Users/Jondy/.pyarmor
INFO     Extracting license.lic
INFO     Extracting .pyarmor_capsule.zip
INFO     This keyfile has been registered successfully.
```

登録されているかどうかを見てみると、このように出力されました。

``` python
~ % pyarmor register
INFO     PyArmor Version 6.5.2
PyArmor Version 6.5.2
Registration Code: pyarmor-vax-000383
Because of internet exception, could not query registration information.
```

`api.dashingsoft.com` のドメインに `ping` を打ち、ipアドレスがこのように解決されていることを確認してください。

``` python
~ % ping api.dashingsoft.com

PING api.dashingsoft.com (119.23.58.77): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
```

そうでない場合は、 `/etc/hosts` に1行追加してください。

``` python
119.23.58.77 pyarmor.dashingsoft.com
```

## 既知の問題点

### Obfuscate scripts in cross platform

v5.6.0からv5.7.0まで、クロスプラットフォームに関するバグがあります。linux64/windows64/darwin64で難読化されたスクリプトを、これらのターゲットプラットフォームにコピーすると、動作しなくなります。

``` python
armv5, android.aarch64, ppc64le, ios.arm64, freebsd, alpine, alpine.arm, poky-i586
```

## ライセンスに関する質問

[Q&A](https://pyarmor.readthedocs.io/en/latest/license.html#license-questions)を参照してください。

## その他 質問

### 難読化されたコードの復元は容易か？

もし誰かが難読化を破ろうとするならば、まずリバースエンジニアの分野の専門家であり、Pythonの専門家であり、Pythonのコードオブジェクトの構造、Pythonインタープリタの各命令を理解しなければなりません。そのような人がリバースエンジニアリングを始めて、何千もの機械命令を順を追って調べ、機械コードでアルゴリズムを調査しなければなりません。ですので、pyarmorを逆引きするのは簡単なことじゃありません。

### 領収書・請求書の発行方法
Pyarmorの販売はすべてMyCommerceで行っています。
注文、領収書、請求書の発行については、こちらのページをご参照ください。
[https://www.mycommerce.com/shopper-support/](https://www.mycommerce.com/shopper-support/)

または、"ClientSupport@MyCommerce.com "に直接お問い合わせください。

## pyarmorの評価版ライセンスはありますか？
申し訳ありませんが、pyarmorのライセンスはオフラインでも動作しますので、評価用ライセンスはありません。

通常、難読化されたスクリプトは、元のスクリプトをシームレスに置き換えることができます。ただし、pyarmorによって変更された機能を除いてです。 [難読化されたスクリプトの違いについて](#難読化されたスクリプトの違いについて) にリストしています。

ほとんどのパッケージはpyarmorで動作し、一部のパッケージはパッチを当てるだけでpyarmorも動作します。バイトコードやこのようなものを閲覧するパッケージはpyarmorでは全く動作しません。

# 17.ライセンス

本ソフトウェアは、　<font color=blue>Free To Use But Restricted </font>として配布されます。無料体験版には有効期限はありません。

+ 試用版では、大きなスクリプトを難読化することができません。
+ 試用版で難読化されたスクリプトは非公開ではありません。つまり、誰でもこれらの難読化されたスクリプトのライセンスファイルを生成することができます。
+ 試用版では、最新のダイナミックライブラリをダウンロードすることができず、旧バージョンが利用可能です。
+ スーパープラスモードは、試用版では利用できません。
+ 試用版では、商用製品のPythonスクリプトを許可なく難読化することはできません。

難読化されたスクリプトのライセンスファイルについては、[難読化スクリプトのライセンスファイル](#難読化スクリプトのライセンスファイル) を参照ください。

上記の制限を解除するためには、ライセンスコードが必要です。
 
ソフトウェアに対して発行されるライセンスには、次の2つの基本的なタイプがあります。

+ ホームユーザー向けの個人ライセンス。ユーザーは、自分のコンピューターでソフトウェアを使用するために1つのライセンスを購入します。この種のライセンスを注文するときは、登録名として本名を入力してください。このソフトウェアは、この登録名に対してのみ許可されています。
ホームユーザーは、個人ライセンスを使用して、ライセンス所有者の所有物であるすべてのPythonスクリプトを難読化し、難読化されたスクリプトのプライベートライセンスファイルを生成し、それらと必要なすべてのファイルを他のマシンまたはデバイスに配布できます。
ホームユーザーは、ライセンス所有者の所有物ではないPythonスクリプトを難読化することはできません。

+ ビジネスユーザー向けのエンタープライズライセンス。ユーザーは、組織の1つの製品シリアルにソフトウェアを使用するために1つのライセンスを購入します。この種のライセンスを注文するときは、組織名と製品名を入力してください。このソフトウェアは、この登録名に対してのみ許可されています。
1つの製品シリアルには、同じ製品の現在のバージョンと他の新しいバージョンが含まれます。
ビジネスユーザーは、すべてのコンピューターと組み込みデバイスでエンタープライズライセンスを使用して、この製品シリアルのすべてのpythonスクリプトを難読化し、これらの難読化されたスクリプトのプライベートライセンスファイルを生成し、それらと必要なすべてのファイルを他のマシンとデバイスに配布できます。
ソフトウェア所有者の許可なしに、ある製品シリアル用に購入したライセンスを他の製品シリアル用に使用しないでください。ビジネスユーザーは、さまざまな製品シリアルの新しいライセンスを購入する必要があります。
いずれの場合も、ソフトウェアは、許可された個人または企業が所有するPythonスクリプトを難読化するためにのみ使用されます。

## 購入方法

ライセンスを購入するには、以下のコマンドを実行してください。

`pyarmor register -buy`

または、Webブラウザで次のURLを開いてください。

`https://order.shareit.com/cart/add?vendorid=200089125&PRODUCT[300871197]=1`

個人ライセンスの場合、注文の際に登録名を本名で記入してください。

企業ライセンスの場合は、登録名に企業名を記入し、「License To Product」に本ソフトウェアを使用する製品名を記入してください。

支払いが正常に完了すると、登録ファイル（一般に「pyarmor-regcode-xxxx.txt」と呼ばれる）が電子メールで送信されます。

それをディスクに保存してから、次のコマンドを実行して PyArmor を登録します。

`pyarmor register /path/to/pyarmor-regcode-xxxx.txt`

登録情報を確認します。

`pyarmor register`

登録に成功したら、試用版で難読化したスクリプトをすべて削除し、再度難読化します。

:::note info
PyArmorのバージョンが6.5.2未満の場合、登録ファイル「pyarmor-regcode-xxxx.txt」をテキストエディタで開き、その中のガイドに従ってPyArmorを登録してください。
:::

:::note warn
レジストレーションコードは永久的に有効であり、永久的に使用することができますが、将来のバージョンでは動作しない可能性があり、将来のバージョンを使用する場合は新しいライセンスを購入する必要がある場合があります。
:::

## アップグレードの注意事項

2019-10-10以前に購入されたライセンスは、最新版へのアップグレードに対応しておりません。最新版をご利用になるには、新しいライセンスが必要です。

## テクニカルサポート

もし何か疑問があれば、まず [質問と解決策](#16うまくいかないとき) をチェックしてください、あなたが問題を迅速に解決するのに役立つ可能性があります。

解決策がない場合、技術的な問題については、[ここ](https://github.com/dashingsoft/pyarmor/issues) をクリックして、テンプレートに従って問題を報告してください。　ビジネスやセキュリティの問題については、 `pyarmor@163.com` までメールを送信してください。

一般的に、メールで送られたその他の問題はすべて無視されます。

問題には3種類あります。

1. 難読化されたスクリプトの制限、または既知の問題と呼ばれるもの
2. PyArmor の不具合
3. 誤った使用方法

最初のカタログについては、修正することができません。例えば、コードオブジェクトのco_codeを訪問するためにinspectを使う、難読化されたスクリプトをトレースするためにpdbを使う、など。これらはすべて動作しません。これらは既知の問題です。 [難読化されたスクリプトの違いについて](#難読化されたスクリプトの違いについて) に、すべての既知の問題をリストアップしています。

2番目のカタログについては、私が修正する義務があります。

それ以外は、ドキュメントを読んで修正するのがあなたの役目です。私は、ヒントや例を与えることはあっても、どのコマンドやオプションを使ってスクリプトを難読化すべきかを手取り足取り教えることはしません。
Microsoft Excelを購入し、複雑なグラフを作りたいとします。あなたはエクセルの高度な機能を学び、そして自分でこのチャートを作らなければなりません。マイクロソフトに頼んで複雑なグラフを作ってもらうわけにはいかないですよね。

同様に、pyarmorは多くの機能と優れたドキュメントを提供していますが、あなたは自分でそれらを学ぶ必要があります。例えば、制限モードはPyArmorの高度な機能ですが、私の役目はドキュメントに書かれている通りに実装することです。あなたのスクリプトを読んで、あなたのスクリプトを制限モードに適合させるのは、私の役目ではありません。

サードパーティのパッケージを難読化または使用する予定がある場合は、このパッケージを難読化して、pyarmorと互換性があることを確認することもできません。[難読化されたスクリプトの違いについて](#難読化されたスクリプトの違いについて)  にリストされています。 パッケージが難読化されたスクリプトによって変更されたこれらの機能を使用している場合、pyarmorでは機能しません。

Pythonの世界には数え切れないほどの大きなパッケージがあり、その中には私が使ったことのない、あるいは全く知らないパッケージもたくさんあります。また、複雑なパッケージを調べて、どの行がpyarmorと衝突するかを見つけるのは簡単ではありませんし、これらの複雑なパッケージをすべてローカルマシンで実行するのは困難です。

一般的にこのような場合、ユーザーは例外の周りの簡単なスナップショットコードやいくつかの実行情報を提供し、私はそれを解析してpyarmorと競合する可能性がある場所を見つけ、解決策を見つけるようにします。

## Q&A

1. 1つのPyArmorライセンスを購入すると、難読化のために様々なマシンで使用できますか？ それとも1台のマシンでのみ有効ですか？1台のマシンにライセンスをインストールし、難読化したコードを配布する必要があるのでしょうか？
-> 様々なマシンで使用できますが、1つのライセンスは1つの製品にのみ有効です。

2. 1つのライセンスで、WindowsやLinuxなど様々なプラットフォームで動作するPythonコードの難読化が可能ですか？
-> 現在のバージョンのすべての機能については、イエスです。しかし、将来のバージョンでは。 1つのライセンスで、PyArmorがサポートするすべてのプラットフォームで使用できるかどうかはわかりません。

3. 購入したライセンスはどれくらいの期間有効ですか？永久ですか？
-> 永久に有効です。 ただし、PyArmorの将来のすべてのバージョンで機能することを約束することはできません。将来のバージョンを使用する場合は、新しいライセンスを購入する必要があるかもしれません。

4. 1つのライセンスで、様々なバージョンのPythonパッケージ/モジュールの難読化を行うことはできますか？
-> はい、1つの製品に属する場合のみ可能です。

5. 問題が発生した場合、サポートは受けられますか？
-> githubで問題を報告し、上記のテクニカルサポートのセクションを参照してください。

6. Pyarmorは様々なPythonのバージョンで動作しますか？
-> ほとんどの機能はPython27、Python30〜Python310で動作しますが、いくつかの機能はPython27、Python37以降でのみ動作します。一部の機能はPython27, Python37以降でしか動作しない可能性があります。

7. 将来リリースされるPythonバージョンをサポートするためにPyArmorを維持する計画はありますか？
-> はい。 PyArmorの目標は、Pythonを商用ソフトウェアで広く使用できるようにすることです。

8. PyArmorで、モジュールが同じ製品に属しているかどうかを識別する仕組みはどのようなものですか？
-> PyArmorはそれ自体を識別できませんが、難読化されたスクリプトをチェックして、どの登録ユーザーがそれらを配布しているかを見つけることができます。 したがって、2つの製品が1つの同じライセンスで配布されていることがわかります。

9. 製品が改訂され、バージョンが変わった場合、同じライセンスを使用できますか、それとも新しいライセンスが必要ですか？
-> 同じライセンスであれば問題ありません。

10. PyArmor EULAの製品連載とはどういう意味ですか？
->製品シリアルとは、販売単位とそのアップグレードバージョンを意味します。 たとえば、個人用のpyarmor、企業用のpyarmor、バージョン1.0から7.0までのpyarmor、およびpyarmorのgrahpicsユーザーインターフェイスであるpyarmor-webuiは、すべて1つの製品シリアルに属しています。