#!/usr/bin/env python3
# === StraightArc Technologies Open Toolset : banner & disclaimer =============
def _sta_accept():
    import sys, os
    line = "=" * 68
    print(line)
    print("               StraightArc Technologies Open Toolset")
    print(line)
    print('DISCLAIMER: This tool is provided on an "AS IS" basis. StraightArc')
    print("Technologies or any of its associates shall not be responsible for")
    print("the consequences of its use. Always operate on a COPY of the media,")
    print("never the original evidence.")
    print(line)
    if os.environ.get("STRAIGHTARC_ACCEPT") == "1":
        return
    if not sys.stdin.isatty():
        sys.exit("Non-interactive run: set STRAIGHTARC_ACCEPT=1 to proceed.")
    try:
        ans = input("Type 'accept' to acknowledge and continue: ").strip().lower()
    except (EOFError, KeyboardInterrupt):
        sys.exit("\nDisclaimer not accepted. Exiting.")
    if ans not in ("accept", "y", "yes"):
        sys.exit("Disclaimer not accepted. Exiting.")
_sta_accept()
# ============================================================================

"""
Rebuild playable MP4s from .Funder video tails.

Original .Funder layout:  [0..384KB encrypted] [384KB..EOF-72 plaintext tail] [72B footer]
Typical MP4 layout:        ftyp + mdat(huge) + moov(at end)  -> the tail holds (rest of mdat)+moov.

Reconstruction preserves ABSOLUTE byte offsets so moov's sample tables (stco) stay valid:
  new file = [synth ftyp] [mdat header] [zeros for lost 384KB region] [tail up to moov] [moov...]
The tail's real bytes land back at their original absolute offsets, so all chunks at/after 384KB
resolve correctly. Only samples inside the first 384KB decode as garbage (corrupted intro).
"""
import struct, os, sys, glob

BND=393216; FOOT=72
def ftyp():
    body=b"isom"+struct.pack(">I",0x200)+b"isom"+b"iso2"+b"avc1"+b"mp41"
    return struct.pack(">I",8+len(body))+b"ftyp"+body

def find_moov(tail):
    # find the top-level 'moov' atom: 4-byte size precedes the type
    p=0
    while True:
        q=tail.find(b"moov",p)
        if q<4:
            if q<0: return -1
            p=q+4; continue
        size=struct.unpack_from(">I",tail,q-4)[0]
        if 8<=size<=len(tail)-(q-4)+16:   # plausible atom size
            return q-4
        p=q+4

def rebuild(tail_path, out_path):
    tail=open(tail_path,"rb").read()
    mo=find_moov(tail)
    if mo<0: return None
    moov=tail[mo:]
    # absolute offset where tail begins in the original file:
    tail_abs=BND
    ft=ftyp()
    # mdat spans from just after ftyp to the absolute start of moov
    moov_abs = tail_abs + mo
    mdat_start = len(ft)
    mdat_total = moov_abs - mdat_start           # includes 8-byte header
    mdat_data_len = mdat_total - 8
    # data = zeros for the lost region, then the tail's mdat bytes (tail[0:mo])
    lost = tail_abs - (mdat_start+8)             # zero-fill length for encrypted region
    if lost < 0: return None
    with open(out_path,"wb") as o:
        o.write(ft)
        o.write(struct.pack(">I", mdat_total if mdat_total<2**32 else 1)+b"mdat")
        if mdat_total>=2**32:
            o.write(struct.pack(">Q", mdat_total))
        o.write(b"\x00"*lost)      # lost encrypted region
        o.write(tail[:mo])         # recovered mdat frames (at correct absolute offsets)
        o.write(moov)              # index
    return out_path, len(moov), lost, mdat_data_len

def validate(path):
    """walk top-level atoms; return list of (type,size)."""
    d=open(path,"rb"); atoms=[];
    import os as _os
    size=_os.path.getsize(path); off=0
    f=open(path,"rb")
    while off < size-8:
        f.seek(off); hdr=f.read(8)
        if len(hdr)<8: break
        sz=struct.unpack(">I",hdr[:4])[0]; typ=hdr[4:8]
        if sz==1:
            f.seek(off+8); sz=struct.unpack(">Q",f.read(8))[0]
        if sz<8: break
        atoms.append((typ.decode('latin1'),sz)); off+=sz
    return atoms

if __name__=="__main__":
    srcs = sys.argv[1:] or (glob.glob("/mnt/d/MS/laptop/salvaged/*.tail") +
                            sorted(glob.glob("/mnt/d/MS/tails/video/*.tail"))[:3])
    OUT="/mnt/d/MS/reconstructed_videos"; os.makedirs(OUT,exist_ok=True)
    for s in srcs:
        base=os.path.basename(s)
        if base.endswith(".tail"): base=base[:-5]
        if "_" in base and base.split("_",1)[0].isdigit(): base=base.split("_",1)[1]
        out=os.path.join(OUT, base if base.lower().endswith(('.mp4','.mov')) else base+".mp4")
        r=rebuild(s,out)
        if not r:
            print(f"  SKIP {base}: no usable moov"); continue
        _,moovlen,lost,mdat=r
        at=validate(out)
        print(f"  {base}: moov={moovlen}B, mdat~{mdat//1024}KB, corrupted-intro={lost//1024}KB")
        print(f"       atoms: {[(t,s) for t,s in at]}")


# ============================================================================
# DISCLAIMER
# This script is provided on an "AS IS" basis. StraightArc Technologies or any
# of its associates shall not be responsible for the consequences of its use.
#
# A Contribution from StraightArc Technologies (info@straightarc.com)
# ============================================================================
