Ë
    Í—Äjƒ  ã                   ó  — d dl Z d dlZd dlZd dlZd dlmZ d dlmZ d dlmZ d dl	m
Z
 d dlmZmZmZmZmZmZmZ ddlmZ e
 G d	„ d
«      «       Zdedeeeef   fd„Z ed„  ed«      D «       «      Zdedefd„Z G d„ d«      Zy)é    N)Úfnmatch)Úmd5)ÚPath)Ú	dataclass)ÚDictÚOptionalÚBinaryIOÚListÚTupleÚUnionÚAnyé   )Úread_cstring_streamc                   ór   — e Zd ZU dZeed<   eed<   eed<   eed<   eed<   dZee   ed<   e	d	e
d
d fd„«       Zy)ÚVPKDirectoryEntrya  Metadata for a file entry within a VPK archive.

    Contains information needed to locate and extract the file data.

    Attributes:
        crc: CRC32 checksum of the file data.
        preload_bytes: Number of bytes stored inline in the directory.
        archive_index: Which archive file contains the data (0x7FFF = directory file).
        entry_offset: Byte offset within the archive file.
        entry_length: Size of the file data in bytes.
        preload_data: Inline data stored in the directory (if any).
    ÚcrcÚpreload_bytesÚarchive_indexÚentry_offsetÚentry_lengthNÚpreload_dataÚfileÚreturnc                 ó^  — t        j                  d|j                  d«      «      } | |d   |d   |d   |d   |d   ¬«      }|j                  dkD  r |j                  |j                  «      |_        t        j                  d	|j                  d«      «      d   }|d
k7  rt        d|d›�«       |S )Nú<IHHIIé   r   r   é   é   é   )r   r   r   r   r   ú<Héÿÿ  z/VPK WARNING: Expected 0xFFFF terminator, got 0xÚ04X)ÚstructÚunpackÚreadr   r   Úprint)Úclsr   ÚdataÚentryÚ
terminators        úVC:\Users\pivot\Documents\cueki\casual-preloader\Lib\site-packages\valve_parsers/vpk.pyÚ	from_filezVPKDirectoryEntry.from_file&   s«   € ä�}‰}˜X t§y¡y°£}Ó5ˆáØ�Q‘Ø˜q™'Ø˜q™'Ø˜a™Ø˜a™ô
ˆð ×Ñ Ò"Ø!%§¡¨5×+>Ñ+>Ó!?ˆEÔä—]‘] 4¨¯©°1«Ó6°qÑ9ˆ
Ø˜ÒÜÐCÀJÈsÐCSÐTÔUàˆó    )Ú__name__Ú
__module__Ú__qualname__Ú__doc__ÚintÚ__annotations__r   r   ÚbytesÚclassmethodr	   r,   © r-   r+   r   r      sU   … ñð 
ƒHØÓØÓØÓØÓØ$(€L�(˜5‘/Ó(àð˜Xð Ð*=ò ó ñr-   r   Úfilepathr   c                 óì   — | j                  dd«      j                  «       } | j                  d«      }|dk\  r| d| }| |dz   d }nd}| }|j                  d«      }|dkD  r|d| }||dz   d }n|}d}|||fS )z¨Parse a file path into VPK components (extension, directory, filename).

    Also normalizes paths to lowercase as Source engine VPK files use lowercase filenames.
    Ú\Ú/r   Nr   Ú Ú.)ÚreplaceÚlowerÚrfind)r7   Ú
last_slashÚ	directoryÚfilename_extÚlast_dotÚfilenameÚ	extensions          r+   Ú_parse_vpk_pathrF   <   s¥   € ð
 ×Ñ  cÓ*×0Ñ0Ó2€Hà—‘ Ó$€JØ�Q‚Ø˜[˜jÐ)ˆ	Ø 
¨Q¡Ð 0Ð1‰àˆ	Øˆà×!Ñ! #Ó&€HØ�!‚|Ø 	 Ð*ˆØ  ¨A¡ Ð0‰	àˆØˆ	à�i Ð)Ð)r-   c              #   ó<   K  — | ]  }d |cxk  rdk  rŒn |–— Œ y­w)é    é~   Nr6   )Ú.0Úbs     r+   Ú	<genexpr>rL   V   s   è ø€ ÐE¡*˜Q°R¸1´^Àµ^”q¡*ùs   ‚“	é   r   c           	      óô   — t        | d¬«      }|j                  dt        «      }|rC|sA|j                  dt        t	        dd«      «      «      }t        d|j                  «       › �«       |j                  d«      S )ztRead a null-terminated ASCII string, filtering non-printable bytes
    (paranoia for corrupt VPK directory entries).T)ÚchunkedNrH   é   z&[VPK] All chars filtered, bad_result: Úascii)r   Ú	translateÚ_NON_PRINTABLEr4   Úranger&   ÚhexÚdecode)r   ÚrawÚresultÚbads       r+   Ú_read_null_stringrZ   Y   se   € ô ˜d¨DÔ
1€CØ�]‰]˜4¤Ó0€Fá
‘6Ø�m‰m˜D¤%¬¨b°#«Ó"7Ó8ˆÜÐ6°s·w±w³y°kÐBÔCà�=‰=˜Ó!Ð!r-   c            
       óL  — e Zd ZdZd/deeef   defd„Zd„ Z	d0d„Z
defd„Zd	edefd
„Zd„ Zd„ Zd	edededee   fd„Z	 d1dee   dee   dee   fd„Zdedee   fd„Zdedee   fd„Zdedeeeeef      fd„Zdedeeef   defd„Z	 d2dedededefd„Ze	 d3deeef   d eeef   d!edefd"„«       Zed#„ «       Zed4d$„«       Z ed3d%„«       Z!ed&„ «       Z"ed'„ «       Z#ed(„ «       Z$d5d)„Z%	 d3d*eeef   dee   dedz  fd+„Z&dedee   fd,„Z'dedefd-„Z(dedee)ee*f      fd.„Z+y)6ÚVPKFilea5  A parser for Valve Package (VPK) files.

    VPK files are Valve's archive format used in Source engine games to store
    game assets like textures, models, sounds, and scripts.

    Args:
        vpk_path: Path to the VPK file. Can be a single .vpk file or a _dir.vpk file
                 for multi-file archives. Accepts str or Path objects.
        auto_parse: Whether to automatically parse the directory on initialization.
                   Defaults to True for convenience.

    Example:
        >>> vpk = VPKFile("pak01_dir.vpk")  # Auto-parses by default
        >>> files = vpk.list_files()
        >>> vpk.extract_file("materials/example.vmt", "output.vmt")
        >>>
        >>> # manual parsing if needed
        >>> vpk = VPKFile("pak01_dir.vpk", auto_parse=False)
        >>> vpk.parse_directory()
    Úvpk_pathÚ
auto_parsec                 óÚ   — t        |«      | _        i | _        | j                  «        d| _        d| _        i | _        | j                  s| j                  «        |r| j                  «        y y )Nr   F)
Ústrr]   rA   Ú_setup_pathsÚ_header_and_tree_offsetÚ_parsedÚ_open_archivesÚ_is_dir_vpkÚ!_calculate_header_and_tree_offsetÚparse_directory)Úselfr]   r^   s      r+   Ú__init__zVPKFile.__init__|   s`   € Ü˜H›ˆŒØMOˆŒØ×ÑÔØ'(ˆÔ$ØˆŒØ35ˆÔØ×ÒØ×2Ñ2Ô4ÙØ× Ñ Õ"ð r-   c                 ó*  — | j                   j                  d«      r-d| _        | j                   | _        | j                   dd | _        yt        t        | j                   «      j                  d«      «      }|dd j                  «       r|d   dk(  r|dd }|› d�}t        |«      j                  «       rd| _        || _        || _        yd	| _        | j                   | _        t        t        | j                   «      j                  d«      «      | _        y)
zJSet up paths for VPK file(s) - determines if single or multi-file archive.ú_dir.vpkTNiøÿÿÿÚ éýÿÿÿéüÿÿÿÚ_F)
r]   Úendswithre   Ú	_dir_pathÚ
_base_pathr`   r   Úwith_suffixÚisdigitÚexists)rh   Úpath_without_extÚpossible_dir_paths      r+   ra   zVPKFile._setup_pathsˆ   sî   € à�=‰=×!Ñ! *Ô-Ø#ˆDÔØ!Ÿ]™]ˆDŒNØ"Ÿm™m¨C¨RÐ0ˆD�Oä"¤4¨¯©Ó#6×#BÑ#BÀ2Ó#FÓGÐØ  Ð$×,Ñ,Ô.Ð3CÀBÑ3GÈ3Ò3NØ#3°C°RÐ#8Ð à#3Ð"4°HÐ =ÐäÐ%Ó&×-Ñ-Ô/Ø#'�Ô Ø!2�”Ø"2�•à#(�Ô Ø!%§¡�”Ü"%¤d¨4¯=©=Ó&9×&EÑ&EÀbÓ&IÓ"J�•r-   r   c                 óì  — t        | j                  d«      5 }t        j                  d«      }|j	                  |«       	 t        |«      }|sn‹	 t        |«      }|sn{	 t        |«      }|snkt        j                  |«      }|| j                  vri | j                  |<   || j                  |   vri | j                  |   |<   || j                  |   |   |<   ŒyŒ‰Œ™	 ddd«       d| _	        | S # 1 sw Y   ŒxY w)aM  Parse the VPK directory structure to enable file operations.

        Must be called before using file listing, extraction, or patching methods.

        Returns:
            Self for method chaining.

        Raises:
            IOError: If the VPK file cannot be read.
            ValueError: If the VPK format is invalid.
        Úrbú<7ITN)
Úopenrq   r#   ÚcalcsizeÚseekrZ   r   r,   rA   rc   )rh   ÚfÚtree_offsetrE   ÚpathrD   r)   s          r+   rg   zVPKFile.parse_directoryž   sý   € ô �$—.‘. $Ô'¨1Ü Ÿ/™/¨%Ó0ˆKØ�F‰F�;ÔàÜ-¨aÓ0�	Ù ØàÜ,¨QÓ/�DÙØàÜ#4°QÓ#7˜Ù'Ø!ä 1× ;Ñ ;¸AÓ >˜à$¨D¯N©NÑ:Ø8:˜DŸN™N¨9Ñ5Ø t§~¡~°iÑ'@Ñ@Ø>@˜DŸN™N¨9Ñ5°dÑ;àDI˜Ÿ™ yÑ1°$Ñ7¸ÑAð ð ð ð ÷ (ð6 ˆŒØˆ÷9 (Ð'ús   —CC*Ã*C3c                 ót  — 	 t        | j                  d«      5 }|j                  d«      }t        |«      dk7  rt	        dt        |«      › �«      ‚t        j                  d|dd «      d   }d|z   | _        | j                  cddd«       S # 1 sw Y   yxY w# t        $ r}t        d	|› �«       Y d}~yd}~ww xY w)
z.Calculate offset for single-file VPK archives.ry   é   z+Invalid VPK header: expected 28 bytes, got ú<Ié   é   r   Nz!Error calculating header offset: )
r{   rq   r%   ÚlenÚ
ValueErrorr#   r$   rb   Ú	Exceptionr&   )rh   r~   ÚheaderÚ	tree_sizeÚes        r+   rf   z)VPKFile._calculate_header_and_tree_offsetÈ   s­   € ð	Ü�d—n‘n dÔ+¨qØŸ™ ›�Ü�v“; "Ò$Ü$ØEÄcÈ&ÃkÀ]ÐSóð ô #ŸM™M¨$°°q¸°Ó=¸aÑ@�	Ø/1°I©~�Ô,Ø×3Ñ3÷ ,×+Ò+ûô ò 	ÜÐ5°a°SÐ9Ô:Üûð	ús5   ‚B ˜A(B
Â 	B Â
BÂB ÂB Â	B7ÂB2Â2B7r   c                 óz   — | j                   s| j                  S |dk(  r| j                  S | j                  › d|d›d�S )Néÿ  ro   Ú03dú.vpk)re   r]   rq   rr   )rh   r   s     r+   Ú_get_archive_pathzVPKFile._get_archive_pathÙ   sB   € Ø×ÒØ—=‘=Ð à˜FÒ"Ø—>‘>Ð!Ø—/‘/Ð" ! M°#Ð#6°dÐ;Ð;r-   c                 ó–   — | j                   j                  «       D ]  }|j                  «        Œ | j                   j                  «        y)z&Close all cached archive file handles.N)rd   ÚvaluesÚcloseÚclear)rh   r~   s     r+   r“   zVPKFile.closeá   s6   € à×$Ñ$×+Ñ+Ö-ˆAØ�G‰G�Ið .à×Ñ×!Ñ!Õ#r-   c                 ó$   — | j                  «        y ©N)r“   ©rh   s    r+   Ú__del__zVPKFile.__del__ç   s   € Ø�
‰
�r-   ÚoffsetÚsizec                 ój  — | j                  |«      }	 || j                  vrt        |d«      | j                  |<   | j                  |   }|| j                  s| j                  ndz   }|j                  |«       |j                  |«      S # t        t        f$ r}t        d|› d|› �«       Y d }~y d }~ww xY w)Nry   r   zError reading from archive z: )
r�   rd   r{   re   rb   r}   r%   ÚIOErrorÚOSErrorr&   )rh   r   r™   rš   Úarchive_pathr~   Úadjusted_offsetr‹   s           r+   Ú_read_from_archivezVPKFile._read_from_archiveê   sµ   € ð ×-Ñ-¨mÓ<ˆð	Ø 4×#6Ñ#6Ñ6Ü48¸ÀtÓ4L�×#Ñ# LÑ1Ø×#Ñ# LÑ1ˆAØ$Ø48×4DÒ4D�×,Ò,È!ñˆOð �F‰F�?Ô#Ø—6‘6˜$“<ÐøÜœÐ!ò 	ÜÐ/°¨~¸RÀ¸sÐCÔDÜûð	ús   “A4B ÂB2ÂB-Â-B2NrE   r€   c                 ó†  — | j                  «        g }|r|gn| j                  j                  «       }|D ]Š  }|| j                  vrŒ|r|gn| j                  |   j                  «       }|D ]R  }|| j                  |   vrŒ| j                  |   |   D ])  }|dk7  r
|› d|› d|› �n|› d|› �}	|j                  |	«       Œ+ ŒT ŒŒ |S )aº  List all files in the VPK archive.

        Args:
            extension: Filter files by extension (e.g., 'vtf', 'vmt'). Optional.
            path: Filter files by directory path. Optional.

        Returns:
            List of file paths within the archive.

        Example:
            >>> vpk.list_files(extension="vtf")  # All texture files
            >>> vpk.list_files(path="materials/models")  # Files in specific directory
        r;   r:   r<   )Ú_ensure_parsedrA   ÚkeysÚappend)
rh   rE   r€   ÚfilesÚ
extensionsÚextÚpathsÚprD   Ú	full_paths
             r+   Ú
list_fileszVPKFile.list_filesû   sÚ   € ð  	×ÑÔØˆÙ$-�i‘[°4·>±>×3FÑ3FÓ3Hˆ
ãˆCØ˜$Ÿ.™.Ñ(Øá"�T‘F¨¯©°sÑ(;×(@Ñ(@Ó(BˆEÛ�Ø˜DŸN™N¨3Ñ/Ñ/Øà $§¡¨sÑ 3°AÔ 6�Hà34¸²8˜1˜#˜Q˜x˜j¨¨#¨Ñ/ÀHÀ:ÈQÈsÈeÐATð ð —L‘L Õ+ñ	 !7ñ	 ð ð ˆr-   Úpatternc                 óö   — | j                  «        | j                  «       }|j                  d«      r"|D �cg c]  }|j                  |«      sŒ|‘Œ c}S |D �cg c]  }t	        ||«      sŒ|‘Œ c}S c c}w c c}w )a™  Find files matching a pattern.

        Args:
            pattern: Glob pattern to match against file paths.
                    Examples: "materials/*.vmt", "models/player/*"

        Returns:
            List of matching file paths.

        Example:
            >>> vpk.find_files("materials/*.vmt")  # All material files
            >>> vpk.find_files("models/player/*")  # All player model files
        r:   )r¢   r«   rp   Ú
startswithr   )rh   r¬   Ú	all_filesr~   s       r+   Ú
find_fileszVPKFile.find_files   sq   € ð 	×ÑÔØ—O‘OÓ%ˆ	Ø×Ñ˜CÔ Ù(ÓB™y˜!¨A¯L©L¸Õ,A’A˜yÑBÐBÙ$Ó<™9�a¬°°7Õ(;’˜9Ñ<Ð<ùò CùÚ<s   ¶A1ÁA1ÁA6Á*A6rD   c                 ó  — | j                  «        	 |j                  dd«      \  }}|| j                  vry| j                  |   D ]*  }|| j                  |   |   v sŒ|r|dk7  r	|› d|› �c S |c S  y# t        $ r Y yw xY w)av  Find the full path of a file by its name.

        Args:
            filename: Name of the file to search for (e.g., "example.vmt").

        Returns:
            Full path within the VPK if found, None otherwise.

        Example:
            >>> full_path = vpk.find_file_path("example.vmt")
            >>> # Returns something like "materials/models/example.vmt"
        r<   r   Nr;   r:   )r¢   Úrsplitr‡   rA   )rh   rD   Únamer§   r€   s        r+   Úfind_file_pathzVPKFile.find_file_path4  sš   € ð 	×ÑÔð	Ø Ÿ™¨¨QÓ/‰IˆD�#ð �d—n‘nÑ$Øà—N‘N 3Ô'ˆDØ�t—~‘~ cÑ*¨4Ñ0Ò0Ù/3¸Àº˜$˜˜q  
Ð+ÒQÈÒQð (ð øô ò 	Ùð	ús   ’A3 Á3	A?Á>A?r7   c                 ó.  — | j                  «        	 t        |«      \  }}}|| j                  v r=|| j                  |   v r,|| j                  |   |   v r||| j                  |   |   |   fS y# t        t        f$ r}t        d|› �«       Y d}~yd}~ww xY w)zGGet raw directory entry for a file. Use get_file_info() for public API.zError getting file entry: N)r¢   rF   rA   ÚAttributeErrorÚKeyErrorr&   )rh   r7   rE   rA   rD   r‹   s         r+   Ú_get_file_entryzVPKFile._get_file_entryP  sµ   € ð 	×ÑÔð	4Ü-<¸XÓ-FÑ*ˆI�y (ð ˜TŸ^™^Ñ+Ø §¡°	Ñ!:Ñ:Ø §¡¨yÑ 9¸)Ñ DÑDð ØØ—N‘N 9Ñ-¨iÑ8¸ÑBðð ð øô ¤Ð)ò 	4ÜÐ.¨q¨cÐ2×3Ð3Øûð	4ús   ’AA- Á-BÁ<BÂBÚoutput_pathc                 óº  — | j                  |«      }|sy|\  }}}	 | j                  |j                  |j                  |j                  «      }|syt        |d«      5 }|j                  dkD  r'|j                  r|j                  |j                  «       |j                  |«       ddd«       y# 1 sw Y   yxY w# t        $ r}	t        d|	› �«       Y d}	~	yd}	~	ww xY w)a*  Extract a file from the VPK archive to disk.

        Args:
            filepath: Path of the file within the VPK archive.
            output_path: Where to save the extracted file (str or Path).

        Returns:
            True if extraction succeeded, False otherwise.

        Example:
            >>> success = vpk.extract_file("materials/example.vmt", "output.vmt")
            >>> success = vpk.extract_file("materials/example.vmt", Path("output.vmt"))
            >>> if success:
            ...     print("File extracted successfully")
        FÚwbr   NTzError extracting file: )r¸   r    r   r   r   r{   r   r   Úwriterˆ   r&   )
rh   r7   r¹   Ú
entry_inforE   rA   r)   Ú	file_datar~   r‹   s
             r+   Úextract_filezVPKFile.extract_filef  sÓ   € ð  ×)Ñ)¨(Ó3ˆ
ÙØà&0Ñ#ˆ	�9˜eð	Ø×/Ñ/Ø×#Ñ# U×%7Ñ%7¸×9KÑ9KóˆIñ Øä�k 4Ô(¨AØ×&Ñ&¨Ò*¨u×/AÒ/AØ—G‘G˜E×.Ñ.Ô/Ø—‘˜	Ô"÷ )ð
 ÷ )ð
 ûÜò 	ÜÐ+¨A¨3Ð/Ô0Üûð	ús<   œ3B9 ÁB9 ÁAB-Â$B9 Â-B6Â2B9 Â6B9 Â9	CÃCÃCÚnew_dataÚcreate_backupc           	      óÔ  — | j                  |«      }|sy|\  }}}	 t        |«      |j                  k7  r(t        d|› dt        |«      › d|j                  › d�«      ‚| j	                  |j
                  «      }|rg|› d�}t        |«      j                  «       sIt        |d«      5 }	t        |d«      5 }
|
j                  |	j                  «       «       d	d	d	«       d	d	d	«       t        |d
«      5 }|j                  |j                  «       |j                  |«       d	d	d	«       y# 1 sw Y   ŒSxY w# 1 sw Y   ŒWxY w# 1 sw Y   yxY w# t        $ r}t        d|› �«       Y d	}~yd	}~ww xY w)aÖ  Replace a file's contents within the VPK archive.

        Args:
            filepath: Path of the file within the VPK archive to replace.
            new_data: The new file contents as bytes.
            create_backup: Whether to create a backup of the original archive.
                          Backup will have .backup extension.

        Returns:
            True if patching succeeded, False otherwise.

        Note:
            The new data must be exactly the same size as the original file.

        Example:
            >>> with open("my_material.vmt", "rb") as f:
            ...     new_content = f.read()
            >>> success = vpk.patch_file("materials/example.vmt", new_content, create_backup=True)
        FzModified file z does not match original (z != z bytes)z.backupry   r»   Nzrb+TzError patching file: )r¸   r†   r   r‡   r�   r   r   ru   r{   r¼   r%   r}   r   rˆ   r&   )rh   r7   rÀ   rÁ   r½   ro   r)   rž   Úbackup_pathÚsrcÚdstr~   r‹   s                r+   Ú
patch_filezVPKFile.patch_file�  sU  € ð, ×)Ñ)¨(Ó3ˆ
ÙØà ‰ˆˆ1ˆeð	Ü�8‹} × 2Ñ 2Ò2Ü Ø$ X Jð /Ü˜H›� d¨5×+=Ñ+=Ð*>¸gðGóð ð
  ×1Ñ1°%×2EÑ2EÓFˆLáØ!- ¨gÐ6�Ü˜KÓ(×/Ñ/Ô1Ü˜l¨DÔ1°S¼$Ø# Tô;àØŸ	™	 #§(¡(£*Ô-÷;×1ô
 �l EÔ*¨aØ—‘�u×)Ñ)Ô*Ø—‘˜Ô!÷ +ð ÷;ð ;ú×1Ð1ú÷
 +ð ûÜò 	ÜÐ)¨!¨Ð-Ô.Üûð	úsf   œBE Â#D.Â0 D"ÃD.ÃE Ã,-D:ÄE Ä"D+	Ä'D.Ä.D7Ä3E Ä:EÄ?E ÅE Å	E'ÅE"Å"E'Ú
source_dirÚoutput_base_pathÚ
split_sizec                 ó$  — t        |«      }t        |«      }	 |j                  j                  dd¬«       t        |j	                  «       «      }|j                  t        j                  «      s|t        j                  z  }t        |«      }g }t        j                  |«      D ]E  \  }	}
}|D ]:  }t        j                  j                  |	|«      }||d }|j                  ||f«       Œ< ŒG |st        d«       y| j                  |«      }|€<t        |«      j                  d«      s|› d�n
t        |«      }| j                  ||«      S | j!                  |||«      S # t"        $ r,}t        d|› �«       t%        j&                  «        Y d}~yd}~ww xY w)a³  Create a new VPK archive from a directory.

        Args:
            source_dir: Directory containing files to archive (str or Path).
            output_base_path: Base path for the output VPK file(s) (str or Path).
                             For single files: "output.vpk" or "output"
                             For multi-file: "output" (will create output_dir.vpk, output_001.vpk, etc.)
            split_size: Maximum size per archive file in bytes. If None, creates a single file.
                       Example: 100*1024*1024 for 100MB splits.

        Returns:
            True if creation succeeded, False otherwise.

        Example:
            >>> # Single file
            >>> VPKFile.create("my_mod", "my_mod.vpk")
            >>> VPKFile.create(Path("my_mod"), Path("my_mod.vpk"))
            >>>
            >>> # Multi-file with 100MB splits
            >>> VPKFile.create("my_mod", "my_mod", split_size=100*1024*1024)
        T©ÚparentsÚexist_okNz"No files found in source directoryFr�   zError creating VPK: )r   ÚparentÚmkdirr`   Úabsoluterp   ÚosÚsepr†   Úwalkr€   Újoinr¤   r&   Ú_build_vpk_structureÚ_create_single_vpkÚ_create_multi_vpkrˆ   Ú	tracebackÚ	print_exc)r'   rÇ   rÈ   rÉ   Úsource_pathÚbase_output_pathÚ
source_strÚ
source_lenr¥   ÚrootÚdirsÚ	filenamesrD   Úfull_path_strÚrel_path_strÚvpk_structurer¹   r‹   s                     r+   ÚcreatezVPKFile.createÃ  s‰  € ô8 ˜:Ó&ˆÜÐ 0Ó1Ðð%	Ø×#Ñ#×)Ñ)°$ÀÐ)ÔFÜ˜[×1Ñ1Ó3Ó4ˆJà×&Ñ&¤r§v¡vÔ.ØœbŸf™fÑ$�
Ü˜Z›ˆJàˆEÜ)+¯©°Ö)<Ñ%��d˜IÛ )�HÜ$&§G¡G§L¡L°°xÓ$@�MØ#0°°Ð#=�LØ—L‘L -°Ð!>Õ?ñ !*ð *=ñ ÜÐ:Ô;Øà×4Ñ4°UÓ;ˆMàÐ!ô Ð/Ó0×9Ñ9¸&ÔAð (Ð(¨Ñ-äÐ-Ó.ð ð
 ×-Ñ-¨m¸[ÓIÐIð ×,Ñ,Ø!Ð#3°Zóð øô ò 	ÜÐ(¨¨Ð,Ô-Ü×ÑÔ!Üûð	ús%   ˜CE Ã8AE ÅE Å	FÅ#"F
Æ
Fc                 óþ   — i }| D ]i  \  }}t        |«      \  }}}||vri ||<   |||   vri ||   |<   t        |d«      5 }|j                  «       }ddd«       t        |«      |dœ||   |   |<   Œk |S # 1 sw Y   Œ%xY w)z6Build internal VPK directory structure from file list.ry   N)Úcontentrš   r€   )rF   r{   r%   r†   )	r¥   rã   Ú	file_pathrâ   rE   r€   rD   r~   ræ   s	            r+   rÕ   zVPKFile._build_vpk_structure
  s©   € ð ˆÛ',Ñ#ˆI�|Ü(7¸Ó(EÑ%ˆI�t˜Xà Ñ-Ø+-�˜iÑ(Ø˜=¨Ñ3Ñ3Ø13�˜iÑ(¨Ñ.ä�i Ô&¨!ØŸ&™&›(�÷ 'ð #Ü˜G›Ø!ñ8ˆM˜)Ñ$ TÑ*¨8Ò4ð (-ð  Ð÷ 'Ð&ús   ¾A3Á3A<	c           
      ól   — d}d}t        j                  ddd||ddd«      }| j                  |«       ||fS )z#Write VPK header structure to file.r„   r…   rz   i4ªUr   r   é0   )r#   Úpackr¼   )r~   rŠ   Úembed_chunk_lengthÚtree_size_posÚembed_chunk_length_posr‰   s         r+   Ú_write_vpk_headerzVPKFile._write_vpk_header   sH   € ð ˆØ!#ÐÜ—‘Ø�:˜q )Ð-?ÀÀBÈó
ˆð 	
�‰�ŒØÐ4Ð4Ð4r-   c                 ó$  — g }d}|€|n|}t        |j                  «       «      D �]×  }| j                  |j                  d«      dz   «       t        ||   j                  «       «      D �]€  }| j                  |j                  d«      dz   «       t        ||   |   j                  «       «      D �]&  }| j                  |j                  d«      dz   «       |rC|€A||   |   |   }	| j                  t	        j
                  d|	d   d|	d   |	d   |	d   «      «       n–||   |   |   }
|
d	   }t        j                  |«      d
z  }|j                  | j                  «       dz   |t        |«      f«       | j                  t	        j
                  d|dddt        |«      «      «       |t        |«      z  }| j                  t	        j
                  dd«      «       �Œ) | j                  d«       �Œƒ | j                  d«       �ŒÚ | j                  d«       |S )z+Write VPK directory tree structure to file.r   rQ   ó    r   r   Úarchive_idxr™   rš   ræ   ì   ÿÿ r„   r�   r    r!   )Úsortedr£   r¼   Úencoder#   rê   ÚzlibÚcrc32r¤   Útellr†   )r~   rã   Úarchive_entriesÚentry_positionsÚarchive_offsetÚ	structurerE   r€   rD   r)   Ú	file_inforæ   r   s                r+   Ú_write_directory_treezVPKFile._write_directory_tree+  sé  € ð ˆØˆà'4Ð'<‘OÀ-ˆ	ä 	§¡Ó 0×1ˆIØ�G‰G�I×$Ñ$ WÓ-°Ñ5Ô6ä˜y¨Ñ3×8Ñ8Ó:×;�Ø—‘˜Ÿ™ GÓ,¨uÑ4Ô5ä & y°Ñ';¸DÑ'A×'FÑ'FÓ'H× I�HØ—G‘G˜HŸO™O¨GÓ4°uÑ<Ô=á&¨=Ð+@Ø /°	Ñ :¸4Ñ @ÀÑ J˜ØŸ™Ü"ŸK™KØ (Ø % e¡Ø !Ø % mÑ 4Ø % h¡Ø % f¡óõ	ð %2°)Ñ$<¸TÑ$BÀ8Ñ$L˜	Ø"+¨IÑ"6˜Ü"Ÿj™j¨Ó1°JÑ>˜Ø'×.Ñ.ØŸV™V›X¨™\¨>¼3¸w»<ÐHôð Ÿ™¤§¡¨H°c¸1¸fÀaÌÈWËÓ VÔWØ&¬#¨g«,Ñ6˜à—G‘GœFŸK™K¨¨fÓ5Ö6ð3 !Jð6 —‘˜–ð= <ð> �G‰G�EŽNðE 2ðF 	
�‰�ŒàÐr-   c                 ób  — t        «       }| j                  |«       |j                  | j                  |«      «       t        «       }t        «       }| j                  d«       | j                  |«      }|j                  |«       |j                  |j	                  «       «       |j                  |j	                  «       «       | j                  dd«       | j                  |j	                  «       «       | j                  |j	                  «       «       | j                  |j	                  «       «       y)z Write MD5 checksums to VPK file.r   r   N)r   r}   Úupdater%   Údigestr¼   )r~   Ú	dir_startÚdir_sizeÚtree_md5Úchunk_hashes_md5Úfile_md5Úheader_datas          r+   Ú_write_checksumszVPKFile._write_checksumsZ  sÒ   € ô “5ˆØ	�‰ˆyÔØ�‰˜Ÿ™˜xÓ(Ô)ä›5ÐÜ“5ˆØ	�‰ˆqŒ	Ø—f‘f˜YÓ'ˆØ�‰˜Ô$Ø�‰˜Ÿ™Ó)Ô*Ø�‰Ð(×/Ñ/Ó1Ô2à	�‰ˆq�!ŒØ	�‰�—‘Ó!Ô"Ø	�‰Ð ×'Ñ'Ó)Ô*Ø	�‰�—‘Ó!Õ"r-   c                 ó  — 	 t        |d«      5 }t        j                  |«      \  }}|j                  «       }t        j	                  || «      }|j                  «       }||z
  }|j                  «       }	d}
t        | j                  «       «      D ]f  }t        | |   j                  «       «      D ]E  }t        | |   |   j                  «       «      D ]!  }| |   |   |   d   }|j                  |«       Œ# ŒG Œh |j                  «       |	z
  }|D ]A  \  }}}|j                  |«       |j                  t        j                  d|
«      «       |
|z  }
ŒC t        j                  |||«       |j                  |«       |j                  t        j                  d|«      «       |j                  |«       |j                  t        j                  d|«      «       ddd«       y# 1 sw Y   yxY w# t        $ r}t        d|› �«       Y d}~yd}~ww xY w)	z!Create a single-file VPK archive.úw+br   ræ   rƒ   NTz Error creating single-file VPK: F)r{   r\   rî   r÷   rý   ró   r£   r¼   r}   r#   rê   r  rˆ   r&   )rã   r¹   r~   rì   rí   r  rù   Údir_endr  Ú
data_startÚcurrent_offsetrE   r€   rD   ræ   rë   Úposr™   Úlengthr‹   s                       r+   rÖ   zVPKFile._create_single_vpkn  sØ  € ð&	Ü�k 5Ô)¨QÜ8?×8QÑ8QÐRSÓ8TÑ5�Ð5ØŸF™F›H�	ä")×"?Ñ"?ÀÀ=Ó"Q�àŸ&™&›(�Ø" YÑ.�ØŸV™V›X�
ð "#�Ü!'¨×(:Ñ(:Ó(<Ö!=�IÜ & }°YÑ'?×'DÑ'DÓ'FÖ G˜Ü(.¨}¸YÑ/GÈÑ/M×/RÑ/RÓ/TÖ(U˜HØ&3°IÑ&>¸tÑ&DÀXÑ&NØ )ñ'˜Gð ŸG™G GÕ,ñ	 )Vñ !Hð ">ð &'§V¡V£X°
Ñ%:Ð"ã+:Ñ'�C˜ Ø—F‘F˜3”KØ—G‘GœFŸK™K¨¨nÓ=Ô>Ø" fÑ,‘Nð ,;ô
 ×(Ñ(¨¨I°xÔ@à—‘�}Ô%Ø—‘œŸ™ D¨(Ó3Ô4Ø—‘Ð-Ô.Ø—‘œŸ™ DÐ*<Ó=Ô>÷A *ðD ÷E *ðD ûÜò 	ÜÐ4°Q°CÐ8Ô9Üûð	ús5   ‚G' ŽGGÇG' ÇG$Ç G' Ç$G' Ç'	HÇ0HÈHc                 ór  — 	 i }d}d}d}d}	 t        | j                  «       «      D ]ø  }t        | |   j                  «       «      D ]×  }	t        | |   |	   j                  «       «      D ]³  }
| |   |	   |
   }|d   }|d   }||z   |kD  r |dkD  r|r|j                  «        |dz  }d}d}|€|› d|d›d�}t        |d	«      }d}t	        j
                  |«      d
z  }||vri ||<   |	||   vri ||   |	<   ||||dœ||   |	   |
<   |j                  |«       ||z  }||z  }Œµ ŒÙ Œú 	 |r|j                  «        	 |› d�}t        |d«      5 }t        j                  |«      \  }}|j                  «       }t        j                  |d|«       |j                  «       }||z
  }t        j                  |||«       |j                  |«       |j                  t        j                  d|«      «       ddd«       y# |r|j                  «        w w xY w# 1 sw Y   yxY w# t        $ r}t!        d|› �«       Y d}~yd}~ww xY w)z:Create a multi-file VPK archive with size-based splitting.r   Nræ   rš   r   ro   rŽ   r�   r»   rò   )rñ   r™   rš   r   rk   r	  rƒ   TzError creating multi-file VPK: F)ró   r£   r“   r{   rõ   rö   r¼   r\   rî   r÷   rý   r  r}   r#   rê   rˆ   r&   )rã   rÛ   rÉ   rø   Úcurrent_archive_idxÚcurrent_sizeÚcurrent_archive_fileÚcurrent_archive_posrE   r€   rD   rü   ræ   rš   rž   r   Údir_pathÚdir_frì   ro   r  r
  r  r‹   s                           r+   r×   zVPKFile._create_multi_vpk™  sŸ  € ðE	Ø ˆOØ"#ÐØˆLØ#'Ð Ø"#Ðð)1Ü!'¨×(:Ñ(:Ó(<Ö!=�IÜ & }°YÑ'?×'DÑ'DÓ'FÖ G˜Ü(.¨}¸YÑ/GÈÑ/M×/RÑ/RÓ/TÖ(U˜HØ(5°iÑ(@ÀÑ(FÀxÑ(P˜IØ&/°	Ñ&:˜GØ#,¨VÑ#4˜Dà+¨dÑ2°ZÒ?ÀLÐSTÒDTÙ#7Ø$8×$>Ñ$>Ô$@Ø 3°qÑ 8Ð 3Ø/0 Ø7;Ð 4à3Ð;à'7Ð&8¸Ð:MÈcÐ9RÐRVÐ$Wð !-ô 8<¸LÈ$Ó7OÐ 4Ø67Ð 3ä"&§*¡*¨WÓ"5¸
Ñ"B˜Cà(°Ñ?Ø=? °	Ñ :Ø#¨?¸9Ñ+EÑEØCE °	Ñ :¸4Ñ @ð 0CØ*=Ø(,Ø'*ñ	J˜O¨IÑ6°tÑ<¸XÑFð 1×6Ñ6°wÔ?Ø/°4Ñ7Ð/Ø(¨DÑ0™LñG )Vñ !Hñ ">ñN (Ø(×.Ñ.Õ0ð +Ð+¨8Ð4ˆHÜ�h Ô&¨%Ü#*×#<Ñ#<¸UÓ#CÑ �˜qØ!ŸJ™J›L�	ä×-Ñ-¨e°T¸?ÔKàŸ*™*›,�Ø" YÑ.�ä×(Ñ(¨°	¸8ÔDà—
‘
˜=Ô)Ø—‘œFŸK™K¨¨hÓ7Ô8÷ 'ð øñ' (Ø(×.Ñ.Õ0ð (ú÷
 'ð ûÜò 	ÜÐ3°A°3Ð7Ô8Üûð	úsN   ‚
H �DG1 Ä"$H ÅB"H	Ç(H Ç1HÈH È	HÈH ÈH È	H6ÈH1È1H6c                 ó>   — | j                   s| j                  «        yy)z,Ensure directory has been parsed before use.N)rc   rg   r—   s    r+   r¢   zVPKFile._ensure_parsedã  s   € à�|Š|Ø× Ñ Õ"ð r-   Ú
output_dirc           	      ó\  — | j                  «        t        |«      }|j                  dd¬«       d}t        «       }t	        |«      }g }|r˜t        | j                  |«      «      }| j                  D ]n  }	| j                  |	   D ]Z  }
| j                  |	   |
   j                  «       D ]5  \  }}|
dk7  r
|
› d|› d|	› �n|› d|	› �}||v sŒ!|j                  |	|
||f«       Œ7 Œ\ Œp nb| j                  D ]S  }	| j                  |	   D ]?  }
| j                  |	   |
   j                  «       D ]  \  }}|j                  |	|
||f«       Œ ŒA ŒU |j                  d„ ¬«       i }	 |D �]K  \  }	}
}}	 |
dk7  rH|dz   |
z   }||vr-t        |«      j                  dd¬«       |j                  |«       |dz   |z   dz   |	z   }n|dz   |z   dz   |	z   }| j                  |j                  «      }||vrt        |d	«      ||<   ||   }|j                  | j                  s| j                   ndz   }|j#                  |«       |j%                  |j&                  «      }t        |d
«      5 }|j(                  dkD  r'|j*                  r|j-                  |j*                  «       |j-                  |«       ddd«       |dz  }�ŒN 	 |j3                  «       D ]  }|j5                  «        Œ |S # 1 sw Y   Œ9xY w# t.        $ r}t1        d|› �«       Y d}~�Œ d}~ww xY w# |j3                  «       D ]  }|j5                  «        Œ w xY w)a–  Extract multiple files to a directory.

        Args:
            output_dir: Directory to extract files to (str or Path).
            pattern: Optional glob pattern to filter files (e.g., "materials/*.vmt").
                    If None, extracts all files.

        Returns:
            Number of files successfully extracted.

        Example:
            >>> count = vpk.extract_all("extracted_files")
            >>> count = vpk.extract_all(Path("extracted_files"))
            >>> print(f"Extracted {count} files")
            >>>
            >>> # extract only material files
            >>> count = vpk.extract_all("materials", "materials/*.vmt")
        TrË   r   r;   r:   r<   c                 ó>   — | d   j                   | d   j                  fS )Nr   )r   r   )Úxs    r+   Ú<lambda>z%VPKFile.extract_all.<locals>.<lambda>  s   € ¨Q¨q©T×-?Ñ-?ÀÀ1Á×ARÑARÑ,Sr-   )Úkeyry   r»   Nr   zFailed to extract file: )r¢   r   rÏ   Úsetr`   r°   rA   Úitemsr¤   ÚsortÚaddr�   r   r{   r   re   rb   r}   r%   r   r   r   r¼   rˆ   r&   r’   r“   )rh   r  r¬   r¹   Ú	extractedÚcreated_dirsÚoutput_dir_strÚfiles_to_extractÚmatching_filesrE   rA   rD   r)   r7   Úopen_archivesÚ
parent_dirÚfull_output_pathrž   Úarchive_filerŸ   r¾   r~   r‹   s                          r+   Úextract_allzVPKFile.extract_allè  sa  € ð* 	×ÑÔÜ˜:Ó&ˆØ×Ñ $°ÐÔ6àˆ	Ü“uˆÜ˜[Ó)ˆàÐÙÜ  §¡°Ó!9Ó:ˆNØ!Ÿ^œ^�	Ø!%§¡°	Ô!:�IØ+/¯>©>¸)Ñ+DÀYÑ+O×+UÑ+UÖ+W™˜ %ð  )¨CÒ/ð  )˜k¨¨8¨*°A°i°[ÑAà$, :¨Q¨y¨kÐ!:ð !ð
 $ ~Ò5Ø,×3Ñ3Ø!*¨I°xÀÐ Gõñ ,Xñ ";ñ ,ð "Ÿ^œ^�	Ø!%§¡°	Ô!:�IØ+/¯>©>¸)Ñ+DÀYÑ+O×+UÑ+UÖ+W™˜ %Ø(×/Ñ/°¸IÀxÐQVÐ0WÕXñ ,Xñ ";ð ,ð
 	×ÑÑ"SÐÔTàˆð#	%Ü9IÑ5�	˜9 h°ð:Ø  CÒ'Ø%3°cÑ%9¸IÑ%E˜
Ø%¨\Ñ9Ü  Ó,×2Ñ2¸4È$Ð2ÔOØ(×,Ñ,¨ZÔ8Ø+5¸Ñ+;¸hÑ+FÈÑ+LÈyÑ+XÑ(ð +¨SÑ0°8Ñ;¸cÑAÀIÑMð )ð $(×#9Ñ#9¸%×:MÑ:MÓ#N�LØ#¨=Ñ8Ü6:¸<ÈÓ6N˜ lÑ3à#0°Ñ#>�LØ&+×&8Ñ&8Ø<@×<LÒ<L˜×4Ò4ÐRSñ'�Oð !×%Ñ% oÔ6Ø ,× 1Ñ 1°%×2DÑ2DÓ E�IäÐ.°Ô5¸Ø ×.Ñ.°Ò2°u×7IÒ7IØŸG™G E×$6Ñ$6Ô7ØŸ™ 	Ô*÷ 6ð
  ‘N’Iñ; :JðB !.× 4Ñ 4Ö 6�Ø×"Ñ"Õ$ð !7ð Ð÷ 6Ð5ûô !ò :ÜÐ4°Q°CÐ8×9Ò9ûð:ûð !.× 4Ñ 4Ö 6�Ø×"Ñ"Õ$ñ !7úsP   ÅL Å&C-KÉAKÊKÊ(L ËK	ËKË	LË(K<Ë6L Ë<LÌL Ì'L+c                 óÀ  — | j                  «        | j                  |«      }|sy|\  }}}	 | j                  |j                  |j                  |j
                  «      }|syt        «       }|j                  dkD  r'|j                  r|j                  |j                  «       |j                  |«       t        |«      S # t        $ r}t        d|› �«       Y d}~yd}~ww xY w)a†  Read a file's contents directly into memory.

        Args:
            filepath: Path of the file within the VPK archive.

        Returns:
            File contents as bytes, or None if file not found.

        Example:
            >>> data = vpk.get_file_data("materials/example.vmt")
            >>> if data:
            ...     content = data.decode('utf-8')  # for text files
        Nr   zError reading file data: )r¢   r¸   r    r   r   r   Ú	bytearrayr   r   Úextendr4   rˆ   r&   )	rh   r7   r½   rE   rA   r)   r¾   rX   r‹   s	            r+   Úget_file_datazVPKFile.get_file_dataD  sÒ   € ð 	×ÑÔØ×)Ñ)¨(Ó3ˆ
ÙØà&0Ñ#ˆ	�9˜eð	Ø×/Ñ/Ø×#Ñ# U×%7Ñ%7¸×9KÑ9KóˆIñ Øô “[ˆFØ×"Ñ" QÒ&¨5×+=Ò+=Ø—‘˜e×0Ñ0Ô1Ø�M‰M˜)Ô$ä˜“=Ð øÜò 	ÜÐ-¨a¨SÐ1Ô2Üûð	ús   ¬3B< Á AB< Â<	CÃCÃCc                 óH   — | j                  «        | j                  |«      duS )aA  Check if a file exists in the VPK archive.

        Args:
            filepath: Path of the file within the VPK archive.

        Returns:
            True if the file exists, False otherwise.

        Example:
            >>> if vpk.file_exists("materials/example.vmt"):
            ...     print("File found!")
        N)r¢   r¸   )rh   r7   s     r+   Úfile_existszVPKFile.file_existsk  s&   € ð 	×ÑÔØ×#Ñ# HÓ-°TÐ9Ð9r-   c                 óÎ   — | j                  «        | j                  |«      }|sy|\  }}}|j                  |j                  |j                  |j
                  |j                  ||dœS )a  Get comprehensive information about a file.

        Args:
            filepath: Path of the file within the VPK archive.

        Returns:
            Dictionary with file information or None if not found.
            Contains: 'size', 'crc', 'archive_index', 'offset', 'preload_bytes'

        Example:
            >>> info = vpk.get_file_info("materials/example.vmt")
            >>> if info:
            ...     print(f"Size: {info['size']} bytes")
            ...     print(f"CRC: 0x{info['crc']:08X}")
        N)rš   r   r   r™   r   rE   rA   )r¢   r¸   r   r   r   r   r   )rh   r7   r½   rE   rA   r)   s         r+   Úget_file_infozVPKFile.get_file_info{  sm   € ð  	×ÑÔØ×)Ñ)¨(Ó3ˆ
ÙØà&0Ñ#ˆ	�9˜eà×&Ñ&Ø—9‘9Ø"×0Ñ0Ø×(Ñ(Ø"×0Ñ0Ø"Ø"ñ
ð 	
r-   )T)r   r\   )NN)Fr–   )r   r   )r   N),r.   r/   r0   r1   r   r`   r   Úboolri   ra   rg   r2   rf   r�   r“   r˜   r   r4   r    r
   r«   r°   r´   r   r   r¸   r¿   rÆ   r5   rä   ÚstaticmethodrÕ   rî   rý   r  rÖ   r×   r¢   r*  r.  r0  r   r   r2  r6   r-   r+   r\   r\   f   s·  „ ññ*
#  s¨D yÑ!1ð 
#¸tó 
#òKó,(ðT°3ó ð"<¨sð <°só <ò$òðØ ðØ*-ðØ58ðà	�%‰óð$ FJñ#Ø! #™ð#Ø5=¸c±]ð#à	ˆc‰ó#ðJ= #ð =¨$¨s©)ó =ð( sð ¨x¸©}ó ð8Øðà	�%˜˜SÐ"3Ð3Ñ4Ñ	5óð,% Sð %°u¸SÀ$¸YÑ7Gð %ÈDó %ðP EJñ4Øð4Ø',ð4Ø=Að4à	ó4ðl ð
 ñ	Dà˜#˜t˜)Ñ$ðDð    T 	Ñ*ðDð ð	Dð
 
òDó ðDðL ñó ðð* ò5ó ð5ð ò,ó ð,ð\ ñ#ó ð#ð& ñ(ó ð(ðT ñGó ðGóR#ð FJñZØ  T 	Ñ*ðZØ5=¸c±]ðZà	ˆt‰óZðx% cð %¨h°u©oó %ðN: Cð :¨Dó :ð 
 cð 
¨h°t¸CÀ¸H±~Ñ.Fô 
r-   r\   )rÑ   rõ   r#   rØ   r   Úhashlibr   Úpathlibr   Údataclassesr   Útypingr   r   r	   r
   r   r   r   Ú_ior   r   r`   rF   r4   rT   rS   rZ   r\   r6   r-   r+   Ú<module>r:     s–   ðÛ 	Û Û Û Ý Ý Ý Ý !ß D× DÑ Då $ð
 ÷(ð (ó ð(ðV*˜cð * e¨C°°c¨MÑ&:ó *ñ4 ÑE¡%¨¤*ÓEÓE€ð
"˜Hð 
"¨ó 
"÷s
ò s
r-   