俺と何某。

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

俺とPhotoRec 〜TSファイルの救出処理を再考する〜


以前、4GB超のTSファイルについて救出処理を書いてみたが
今回はこれを改善して、汎用的に動作するコードを考えてみた。

以前のコードの問題点

  • 4GB以下のファイルが救出できない
    そういう風に書いていたからだけど

  • 誤認識によるゴミファイルの発生
    先頭から5パケット分のsync_byte(0x47)位置を見て判定していたが、
    これでは誤認識によるゴミファイルが出来てしまうことがある*1

  • PhotoRec-6.12-WIPが固まる
    当方の環境では、復元ファイルの出力先指定でフォルダを変更しようとすると
    "Directory listing in progress..."と出たまま固まってしまう
    Windows版のみ発生する現象?原因調査中…

  • 処理が冗長
    file_ts.c に書いてある処理が file_m2ts.c と一部重複しており冗長。

改善策

  • 判定するパケット数を増やす
    先頭から32パケット分のsync_byte(0x47)位置を判定する処理に変更。
    • 何故32パケットか?
      パケット数を増減させて色々試した結果、この程度かなと。
  • ベースをPhotoRec-6.11.3に変更。file_m2ts.c のみ、6.12-WIPのものを使用する。
  • file_ts.c に書いてあった処理を、file_m2ts.c へ統合。

改善したコード

  • file_m2ts.c
/*

    File: file_m2ts.c

    Copyright (C) 2008 Christophe GRENIER <grenier@cgsecurity.org>
  
    This software is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
  
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
  
    You should have received a copy of the GNU General Public License along
    with this program; if not, write the Free Software Foundation, Inc., 51
    Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

 */

#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_m2ts(file_stat_t *file_stat);
/* M2TS */
static int header_check_m2ts(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_m2ts(const unsigned char *buffer, const unsigned int buffer_size, file_recovery_t *file_recovery);
/* M2T */
static int header_check_m2t(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_m2t(const unsigned char *buffer, const unsigned int buffer_size, file_recovery_t *file_recovery);
/* 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_m2ts= {
  .extension="m2ts",
  .description="MPEG2-TS(.m2ts/.m2t/.tod/.ts)",
  .min_header_distance=0,
  .max_filesize=PHOTOREC_MAX_FILE_SIZE,
  .recover=1,
  .enable_by_default=1,
  .register_header_check=&register_header_check_m2ts
};

static const unsigned char hdmv_header[4] = { 'H','D','M','V'};
static const unsigned char tshv_header[4] = { 'T','S','H','V'};
static const unsigned char sdvs_header[4] = { 'S','D','V','S'};
static const unsigned char ts_header[1] = {0x47};

static void register_header_check_m2ts(file_stat_t *file_stat)
{
  register_header_check(0xd7, hdmv_header, sizeof(hdmv_header), &header_check_m2ts, file_stat);
  register_header_check(0xd7, sdvs_header, sizeof(sdvs_header), &header_check_m2ts, file_stat);
  register_header_check(0x18b, tshv_header, sizeof(tshv_header),  &header_check_m2t,  file_stat);
  register_header_check(0, ts_header, sizeof(ts_header),  &header_check_ts,  file_stat);
}

static int header_check_m2ts(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_m2ts)
    return 0;
  /* BDAV MPEG-2 transport stream */
  /* Each frame is 192 byte long and begins by a TS_SYNC_BYTE */
  if(!(buffer[4]==0x47 && buffer[4+192]==0x47 && buffer[4+2*192]==0x47))
    return 0;
  if( memcmp(&buffer[0xd7], hdmv_header, sizeof(hdmv_header))==0 &&
      memcmp(&buffer[0xe8], hdmv_header, sizeof(hdmv_header))==0)
  {
    reset_file_recovery(file_recovery_new);
#ifdef DJGPP
    file_recovery_new->extension="m2t";
#else
    file_recovery_new->extension=file_hint_m2ts.extension;
#endif
    file_recovery_new->min_filesize=192;
    file_recovery_new->calculated_file_size=192;
    file_recovery_new->data_check=&data_check_m2ts;
    file_recovery_new->file_check=&file_check_size;
    return 1;
  }
  if( memcmp(&buffer[0xd7], sdvs_header, sizeof(sdvs_header))==0 &&
      memcmp(&buffer[0xe8], sdvs_header, sizeof(sdvs_header))==0)
  {
    reset_file_recovery(file_recovery_new);
    file_recovery_new->extension="tod";
    file_recovery_new->min_filesize=192;
    file_recovery_new->calculated_file_size=192;
    file_recovery_new->data_check=&data_check_m2ts;
    file_recovery_new->file_check=&file_check_size;
    return 1;
  }
  return 0;
}

static int header_check_m2t(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_m2ts &&
      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 &&
      memcmp(&buffer[0x18b], tshv_header, sizeof(tshv_header))==0)
  {
    reset_file_recovery(file_recovery_new);
    file_recovery_new->extension="m2t";
    file_recovery_new->min_filesize=188;
    file_recovery_new->calculated_file_size=188;
    file_recovery_new->data_check=&data_check_m2t;
    file_recovery_new->file_check=&file_check_size;
    return 1;
  }
  return 0;
}

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_m2ts &&
      file_recovery->calculated_file_size == file_recovery->file_size)
    return 0;

  unsigned int i = 0;
  while(i < 32)
  {
    /* Each frame is 188 byte long and begins by a TS_SYNC_BYTE */
    if(buffer[i*188]!=0x47)
      return 0;
    i++;
  }
  reset_file_recovery(file_recovery_new);
  file_recovery_new->extension="ts";
  file_recovery_new->min_filesize=188;
  file_recovery_new->calculated_file_size=188;
  file_recovery_new->data_check=&data_check_m2t;
  file_recovery_new->file_check=&file_check_size;
  return 1;
}

static int data_check_m2ts(const unsigned char *buffer, const unsigned int buffer_size, file_recovery_t *file_recovery)
{
  while(file_recovery->calculated_file_size + buffer_size/2  >= file_recovery->file_size &&
      file_recovery->calculated_file_size + 5 < 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+4]!=0x47)	/* TS_SYNC_BYTE */
      return 2;
    file_recovery->calculated_file_size+=192;
  }
  return 1;
}

static int data_check_m2t(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;
}

結果

小さいファイルから4GB超のファイルまで救出できることを確認。
ゴミファイルは完全には無くならなかったものの、大幅に削減できたと思う。

判定するパケット数を更に増やしたり、ファイル最小サイズ(file_recovery_new->min_filesize)を大きめに設定することで
より良い処理になる可能性も。

*1:最小ファイルサイズ(4GB)に満たないゴミファイルは、最終的には自動的に削除されるけど