#!/usr/bin/env bash
# ppee-dump-resource.sh FILE TYPE NAME [OUT] -- carve one resource (first language) out of a PE using ppee-cli's JSON.
# TYPE/NAME are as ppee-cli prints them: RT_RCDATA / DATA.DLL, RT_VERSION / 1, "IMAGE" -> IMAGE, …
set -euo pipefail
f=$1 type=$2 name=$3 out=${4:-"$name.bin"}
read -r off size md5 < <(ppee-cli --no-update-check --no-similarity --json --resources --sections "$f" | jq -r --arg t "$type" --arg n "$name" '
  def h: ascii_downcase | explode | reduce .[] as $c (0; . * 16 + (if $c >= 97 then $c - 87 else $c - 48 end));
  .sections as $s
  | [range(0; 96) | select($s["Section[\(.)].VirtualAddress"] != null)
     | {va: ($s["Section[\(.)].VirtualAddress"] | h), raw: ($s["Section[\(.)].PointerToRawData"] | h),
        size: ($s["Section[\(.)].SizeOfRawData"] | h)}] as $secs
  | .resources.types[] | select(((.typeName // .name // .id) | tostring) == $t)
  | .names[] | select(((.name // .id) | tostring) == $n) | .languages[0]
  | (.offsetToData | h) as $rva
  | [$secs[] | select($rva >= .va and $rva < .va + .size)][0] as $sec
  | if $sec == null then "NOTINFILE \(.size) \(.md5)" else "\($rva - $sec.va + $sec.raw) \(.size) \(.md5)" end' | head -1) || true
[ -n "${off:-}" ] || { echo "resource $type/$name not found (check the names with: ppee-cli --resources $f)" >&2; exit 1; }
[ "$off" != NOTINFILE ] || { echo "resource $type/$name is not backed by file data (packed/virtual section); dump it from memory instead" >&2; exit 2; }
dd if="$f" of="$out" bs=1 skip="$off" count="$size" status=none
got=$(md5sum "$out" | cut -c1-32 | tr a-f A-F)
echo "$out: $size bytes at file offset 0x$(printf %X "$off"), md5 $got $([ "$got" = "$md5" ] && echo OK || echo "MISMATCH (expected $md5)")"
