Pythonでのバイナリファイル編集

# そもそも、バイナリファイルの編集は、どういう場合に行われるか？

実行形式のプログラムファイルや、画像、動画などのファイルを詳しく解析するために用いられることが多い。

# バイナリへの変換の方法

Pythonでは、バイナリのデータ型はbytesです。


# int型の値をbytesに変換する方法1

int.to_bytes()を使う。

第一引数：int：変換後のバイトサイズを指定。例えば4バイトに変換する場合は4を指定。
第二引数：str：バイトオーダーを指定。ビッグエンディアンとリトルエンディアンがある。詳細は後で。
第三引数：bool：2の補数を使うかを指定。詳細は後で。

例
```python
number_int = 1000
print(number_int.to_bytes(4, 'big', signed=False))
number_int2 = -9999999999
print(number_int2.to_bytes(8, 'big', signed=True))
```
実行結果
```
b'\x00\x00\x03\xe8'
b'\xff\xff\xff\xfd\xab\xf4\x1c\x01'
```

バイナリへの変換の方法2

structモジュールのpack()を使う。

アルファベット、記号の意味については[公式ドキュメント](https://docs.python.org/ja/3/library/struct.html)参照。

例：
```python
import struct

number_int = 1000
print(struct.pack('>i', number_int))
number_int2 = -9999999999
print(struct.pack('>q', number_int2))
```

実行結果
```
b'\x00\x00\x03\xe8'
b'\xff\xff\xff\xfd\xab\xf4\x1c\x01'
```

# バイナリからの変換の方法1

int.from_bytes()を使う。

第一引数：bytes：変換対象のバイナリデータを指定。
第二引数：str：バイトオーダーを指定。ビッグエンディアンとリトルエンディアンがある。詳細は後で。
第三引数：bool：2の補数を使うかを指定。詳細は後で。

例
```python
binary = b'\x00\x00\x03\xe8'
print(int.from_bytes(binary, 'big', signed=False))
binary2 = b'\xff\xff\xff\xfd\xab\xf4\x1c\x01'
print(int.from_bytes(binary2, 'big', signed=True))
```
実行結果
```
1000
-9999999999
```


# バイナリからの変換の方法2
structモジュールのunpack()を使う。

```python
import struct

binary = b'\x00\x00\x03\xe8'
print(struct.unpack('>i', binary))
binary2 = b'\xff\xff\xff\xfd\xab\xf4\x1c\x01'
print(struct.unpack('>q', binary2))
```

アルファベット、記号の意味については[公式ドキュメント](https://docs.python.org/ja/3/library/struct.html)参照。


# バイトオーダー（別名：エンディアン）とは？

2バイト以上のバイトがあったときに、そのバイトを並べる順番のこと。
ビッグエンディアンとリトルエンディアンの2種類がある。

ビッグエンディアン：上位バイトから下位バイトの順に扱う。
リトルエンディアン：下位バイトから上位バイトの順に扱う。

2バイトのデータで違いを確認してみる。

```python
number_int = 1000
print(number_int.to_bytes(4, 'big', signed=False))
print(number_int.to_bytes(4, 'little', signed=False))
```

実行結果
```
b'\x00\x00\x03\xe8'
b'\xe8\x03\x00\x00'
```
どちらのバイトオーダーなのかはCPUに依存する。

Intel製、AMD製：伝統的にリトルエンディアン。こちらが主流。
Motorola製：伝統的にビッグエンディアン
※最近のCPUでは、どちらにも切り替えられる「バイエンディアン」を採用しているものもある。

マシンをまたいでバイナリデータをやりとりする場合、バイトオーダーを考慮に入れないと、マシンによって全く異なる値に変換されてしまうため注意が必要。

# 2の補数とは？
負の数を2進法で表す時に用いられる方法。
2進数のビットを反転させて1を足したもの。

※10進法では負の数を表すとき、-（マイナス）をつけるが、2進法では、負の数も、0と1の組み合わせで表す（マイナス記号を使わずに負の数を表す）。

負の数を表したいときは、to_bytes()、from_bytes()の引数signedをTrueにします。


## 2の補数の求め方
10進数の126を、-126にする場合、こうなります。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/a8d91a6a-e6da-bcbf-9e2c-60200f274569.png)

※2の補数の場合、先頭1ビットは必ず「1」になる。

# バイナリファイルの開き方

open関数を使う。
読み込みモードとして、テキストモード（t）とバイナリモード（b）の2種類があり、bを指定する。


```python
try:
    with open('picture.jpg', 'rb') as bin_file:
        file_data = bin_file.read(100)
        print('%dバイト読み込みました。' % len(file_data))
except OSError:
    print('ファイルを開けませんでした。')
```

実行結果
```
100バイト読み込みました。
```

## ビットへの分解方法
Pythonでは、ファイルから読み込むときの最小単位はバイトである。ビット単位で読み込むことはできない。

ビット解析をしたい場合は、**いったんバイト単位で読み込んだ値をビット列に変換**する必要がある。

ビットへの変換を行うサンプルコード

```python
def convert_to_bin(bytes_value: bytes) -> str:
    bin_list = list()
    for byte_value in bytes_value:
        bin_list.append(format(byte_value, '08b'))

    return ''.join(map(str, bin_list))


binary = b'\x00\x00\x03\xe8'
print(convert_to_bin(binary))
```

実行結果
```
00000000000000000000001111101000
```