俺と何某。

底辺プログラマの備忘録/雑記帳/実験場

俺とPhotoRec 〜誤って削除された、4GBを超えるTSファイルを救出する〜

前回のあらすじ

  • WMPの操作を誤って、大量のTSファイルを削除してしまった!
  • 4GB以下のTSファイルであれば、様々なフリーソフトで救出できた。
  • だが、4GBを超えるファイルに対応するソフトがない!
    PhotoRecというソフトは4GB超のファイルも救出できるが、
    残念ながらTS形式に対応していない!諦めるしかないのか!?

しかし幸いにもPhotoRecはオープンソースであり、
比較的簡単に対応するファイル形式を追加できる仕様であった。
なのでソースコードを修正し、4GBを超えるTSファイルを救出してみた。

救出の対象(ソースコードの修正方針)

  • friio/PT1/PT2等が出力する.tsファイル
  • ファイルサイズが4GB*1を超えるもの

作業内容

  1. 救出するファイルフォーマットの調査
    MPEG2-TS形式のファイル(.ts/.m2ts/.m2t)の違いについてなど
  2. ソースコードの追加・修正方法の検討
  3. ソースコードの追加・修正
  4. コンパイル環境の構築
  5. コンパイル
  6. 実行

MPEG2-TS形式について

Wikipedia参照。
MPEG-2システム - Wikipedia

実際のファイルを確認してみる

バイナリエディタでMPEG2-TS形式のファイル(.ts/.m2ts/.m2t)を調査してみる。
.m2ts/.m2tファイルについては、これらを出力する機器を持っていないので
Web上から各ファイルを*2探してきました

  • .m2tsファイル
    • パケット長:192byte
    • ヘッダに含まれる文字列:HDMV
  • .m2tファイル
    • パケット長:188byte
    • ヘッダに含まれる文字列:TSHV?*3
  • .tsファイル
    • パケット長:188byte
    • ヘッダに含まれる文字列:なし?*4

ソースコードからPhotoRec*5の動作を確認

MPEG2-TS形式の救出処理は、file_m2ts.cを参照。

  • .m2tsファイル
    • ファイル検索処理
      0xd7にHDMVが存在すること
    • ヘッダ判定処理
      • 0x04、0xc4(4+192)、0x184(2+4×192)の位置に、sync_byte(0x47)があること
        (先頭から3パケット分のsync_byteをチェックしている模様)
      • かつ、0xd7と0x8eにHDMVが存在すること

  • .m2tファイル
    • ファイル検索処理
      0x18bにTSHVが存在すること
    • ヘッダ判定処理
      • 0x00、0xbc、0x178(2×188)の位置に、sync_byte(0x47)があること
        (先頭から3パケット分のsync_byteをチェックしている模様)
      • かつ、0x18bにTSHVが存在すること

上記から分かること

  • .tsファイルについては、ファイル先頭のsync_byte(0x47)で引っかけるしかなさそう。
  • しかし0x47はパケットの先頭以外(パケット内)にも登場するので、
    単純に0x47だけを見るわけにはいかない。

PhotoRec:対応ファイルフォーマットの追加方法

オフィシャルサイトのwikiを参照。
Developers - CGSecurity

  1. 救出処理を書いたsrc/file_ts.cを新規作成
  2. src/file_list.cに追記
  3. src/Makefile.amに追記

上記を踏まえたコード:file_ts.c

  • ファイル先頭のsync_byte(0x47)でファイルを検索
  • 念の為、先頭5パケット分のsync_byte位置を確認する
  • 最小ファイルサイズを4GB(4,294,967,296byte)に設定*6
  • 後はfile_m2ts.cの処理を流用
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#include <stdio.h>
#include "types.h"
#include "filegen.h"

static void register_header_check_ts(file_stat_t *file_stat);
/* TS */
static int header_check_ts(const unsigned char *buffer, const unsigned int buffer_size, const unsigned int safe_header_only, const file_recovery_t *file_recovery, file_recovery_t *file_recovery_new);
static int data_check_ts(const unsigned char *buffer, const unsigned int buffer_size, file_recovery_t *file_recovery);

const file_hint_t file_hint_ts= {
  .extension="ts",
  .description="MPEG-2 TS(4GB over)",
  .min_header_distance=0,
  .max_filesize=PHOTOREC_MAX_FILE_SIZE,
  .recover=1,
  .enable_by_default=1,
  .register_header_check=&register_header_check_ts
};

static const unsigned char ts_header[1]=  {0x47};

static void register_header_check_ts(file_stat_t *file_stat)
{
  register_header_check(0, ts_header, sizeof(ts_header),  &header_check_ts,  file_stat);
}

static int header_check_ts(const unsigned char *buffer, const unsigned int buffer_size, const unsigned int safe_header_only, const file_recovery_t *file_recovery, file_recovery_t *file_recovery_new)
{
  if(file_recovery!=NULL && file_recovery->file_stat!=NULL &&
      file_recovery->file_stat->file_hint==&file_hint_ts &&
      file_recovery->calculated_file_size == file_recovery->file_size)
    return 0;

  /* Each frame is 188 byte long and begins by a TS_SYNC_BYTE */
  if(buffer[0]==0x47 && buffer[188]==0x47 && buffer[2*188]==0x47 && buffer[3*188]==0x47 && buffer[4*188]==0x47 && buffer[5*188]==0x47)
  {
    reset_file_recovery(file_recovery_new);
    file_recovery_new->extension=file_hint_ts.extension;
    file_recovery_new->min_filesize=4294967296;
    file_recovery_new->calculated_file_size=188;
    file_recovery_new->data_check=&data_check_ts;
    file_recovery_new->file_check=&file_check_size;
    return 1;
  }
  return 0;
}

static int data_check_ts(const unsigned char *buffer, const unsigned int buffer_size, file_recovery_t *file_recovery)
{
  while(file_recovery->calculated_file_size + 1 < file_recovery->file_size + buffer_size/2)
  {
    unsigned int i=file_recovery->calculated_file_size - file_recovery->file_size + buffer_size/2;
    if(buffer[i]!=0x47)	/* TS_SYNC_BYTE */
      return 2;
    file_recovery->calculated_file_size+=188;
  }
  return 1;
}

PhotoRec:コンパイル環境の構築とコンパイル

オフィシャルサイトのwikiを見れば、俺でも理解できたので割愛
TestDisk Compilation - CGSecurity

PhotoRecの使い方

こちらが大変詳しいので割愛
「PhotoRec」の使い方 - PCと解
※[File Opt]の設定で、TSにだけチェック(X印)を入れて実行すること。
他の拡張子と一緒に救出しようとすると、上手く行かない可能性があります。


最後に

当方の環境では、5GB〜33GB程度のファイルでも救出できました。
とはいえあまりスマートじゃない方法*7なので、参考程度でお願いします。
より良い判定方法をご存じの方は、是非教えて戴きたく。

あとかなり雑なエントリなので、いずれ全般的に追記修正したいと思います。


*1:4,294,967,296 Byte

*2:デジタルビデオカメラのサンプル動画など

*3:手に入った.m2tファイルは、ヘッダ部分にTSHVが含まれない様子

*4:ファイルの到る処に大文字小文字が入り交じった「HDMV」が登場するが、位置が不特定な様子

*5:バージョンは6.12-WIP

*6:4GB未満のファイルは対象外とするため

*7:4GB未満のファイル救出をオミットしているため