2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Thumbs.db から JPEG を抜き出す

2
Posted at

動機

Thumbs.db の中のサムネイル画像を抽出してみたくなったので調べました。

環境

  • Windows 11 25H2
  • rustc 1.96.0

実装

Thumbs.db は OLE 構造化ストレージ という形式(古い Word とか Excel で使われていたファイル形式と同じ)なので StgOpenStorage という WinAPI で開けるそうです。IStorage インターフェースを取得できたら EnumElements メソッドでストリームを列挙して、IStream (正確には ISequentialStream)の Read メソッドでバッファにデータを読み込みます。読み込んだデータから JPEG の SOI と EOI を探してファイルに保存しておしまい。意外に簡単なので、コードを全部書きます。

Cargo.toml
[package]
name = "thumbs-db"
version = "0.1.0"
edition = "2024"

[dependencies]
anyhow = "=1.0.102"

[dependencies.windows]
version = "=0.62.2"
features = [
    "Win32_System_Com",
    "Win32_System_Com_StructuredStorage",
]
main.rs
use std::{env, fs, ops::Deref};

use anyhow::{Context, Result};
use windows::{
    Win32::System::Com::{
        CoInitialize, CoTaskMemFree, CoUninitialize, STATSTG, STGM_READ, STGM_SHARE_DENY_WRITE,
        STGM_SHARE_EXCLUSIVE, STGTY_STREAM, StructuredStorage::StgOpenStorage,
    },
    core::HSTRING,
};

// COM の初期化と終了処理を実行するための構造体
struct Com;

impl Com {
    fn new() -> Result<Self> {
        unsafe { CoInitialize(None).ok()? };
        Ok(Self)
    }
}

impl Drop for Com {
    fn drop(&mut self) {
        unsafe { CoUninitialize() };
    }
}

// 生の [STATSTG] のままだと使いにくいのでラップしてメソッドを生やすための構造体
struct Stat([STATSTG; 1]);

impl Stat {
    fn new() -> Self {
        Self([STATSTG::default()])
    }

    fn as_mut_slice(&mut self) -> &mut [STATSTG] {
        &mut self.0
    }

    fn name(&self) -> Result<String> {
        Ok(unsafe { self.pwcsName.to_string() }?)
    }
}

impl Deref for Stat {
    type Target = STATSTG;
    fn deref(&self) -> &Self::Target {
        &self.0[0]
    }
}

// pwcsName のメモリ領域を解放
impl Drop for Stat {
    fn drop(&mut self) {
        unsafe { CoTaskMemFree(Some(self.pwcsName.0 as _)) };
    }
}

fn main() -> Result<()> {
    // impl From<String> for HSTRING が実装されているので into() で HSTRING に変換できる
    let arg: HSTRING = env::args().nth(1).context("no args")?.into();
    // COM の初期化処理
    let _com = Com::new()?;
    // ストレージを開く
    let storage =
        unsafe { StgOpenStorage(&arg, None, STGM_READ | STGM_SHARE_DENY_WRITE, None, 0)? };
    // すべてのストリームを列挙する
    let enm = unsafe { storage.EnumElements(None, None, None)? };
    loop {
        let mut stat = Stat::new();
        // 次のストリームへ移動
        unsafe { enm.Next(stat.as_mut_slice(), Some(&mut 0))? };
        // Next 関数が常に Ok(()) が帰すのでサイズ 0 だったら処理を終了
        if stat.cbSize == 0 {
            break;
        }
        // ストリームのときだけ処理。それ以外はスルー
        if stat.r#type != STGTY_STREAM.0 as _ {
            continue;
        }
        // ストリームを開く
        let stream = unsafe {
              storage.OpenStream(stat.pwcsName, None, STGM_READ | STGM_SHARE_EXCLUSIVE, 0)?
        };
        // ストリームを書き込むバッファを確保
        let mut buf = vec![0u8; stat.cbSize as _];
        // ストリームの内容をバッファに読み込む
        unsafe {
            stream
                .Read(buf.as_mut_ptr() as _, stat.cbSize as _, Some(&mut 0))
                .ok()?
        };
        // JPEG の SOI と EOI を探す
        let mut win = buf.windows(2);
        let start = win
            .position(|v| v.eq(&[0xff, 0xd8]))
            .context("soi not found")?;
        let end = win
            .position(|v| v.eq(&[0xff, 0xd9]))
            .map(|v| v + 2) // EOI の 2bytes 分追加
            .context("eoi not found")?;
        // JPEG としてファイルに保存する
        fs::write(format!("{}.jpg", stat.name()?), &buf[start..end])?;
    }
    Ok(())
    // 自動的に COM の終了処理が実行される
}

使用例

cargo run -- /path/to/Thumbs.db

私の環境では256_2b1e89589ce071a7.jpg(画像サイズ_GUID?.jpg)のようなファイル名で抽出できました。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?