mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-12-02 17:22:32 +00:00
242 lines
6.8 KiB
Python
242 lines
6.8 KiB
Python
|
# a collection of utility functions to manipulate pak files
|
||
|
|
||
|
import os, zipfile, md5, pdb
|
||
|
|
||
|
# sorts in reverse alphabetical order like doom does for searching
|
||
|
def list_paks( path ):
|
||
|
files = os.listdir( path )
|
||
|
for i in files:
|
||
|
if ( i[-4:] != '.pk4' ):
|
||
|
files.remove( i )
|
||
|
files.sort()
|
||
|
files.reverse()
|
||
|
return files
|
||
|
|
||
|
def list_files_in_pak( pak ):
|
||
|
files = []
|
||
|
zippy = zipfile.ZipFile( pak )
|
||
|
files += zippy.namelist()
|
||
|
files.sort()
|
||
|
return files
|
||
|
|
||
|
# no sorting, blunt list of everything
|
||
|
def list_files_in_paks( path ):
|
||
|
files = []
|
||
|
zippies = list_paks( path )
|
||
|
for fname in zippies:
|
||
|
print fname
|
||
|
zippy = zipfile.ZipFile( os.path.join( path, fname ) )
|
||
|
files += zippy.namelist()
|
||
|
# sort and remove dupes
|
||
|
dico = {}
|
||
|
for f in files:
|
||
|
dico[ f ] = 1
|
||
|
files = dico.keys()
|
||
|
files.sort()
|
||
|
return files
|
||
|
|
||
|
# build a dictionary of names -> ( pak name, md5 ) from a path of pk4s
|
||
|
def md5_in_paks( path ):
|
||
|
ret = {}
|
||
|
zippies = list_paks( path )
|
||
|
for fname in zippies:
|
||
|
print fname
|
||
|
zippy = zipfile.ZipFile( os.path.join( path, fname ) )
|
||
|
for file in zippy.namelist():
|
||
|
if ( ret.has_key( file ) ):
|
||
|
continue
|
||
|
data = zippy.read( file )
|
||
|
m = md5.new()
|
||
|
m.update( data )
|
||
|
ret[ file ] = ( fname, m.hexdigest() )
|
||
|
return ret
|
||
|
|
||
|
# find which files need to be updated in a set of paks from an expanded list
|
||
|
# returns ( updated, not_found, {} )
|
||
|
# ignores directories
|
||
|
# by default, no case match is done
|
||
|
# if case match is set, return ( updated, not_found, { zip case -> FS case } )
|
||
|
# updated will contain the zip case name
|
||
|
def list_updated_files( pak_path, base_path, case_match = False ):
|
||
|
not_found = []
|
||
|
updated = []
|
||
|
case_table = {}
|
||
|
pak_md5 = md5_in_paks( pak_path )
|
||
|
for file in pak_md5.keys():
|
||
|
if ( file[-1] == '/' ):
|
||
|
continue
|
||
|
path = os.path.join( base_path, file )
|
||
|
if ( case_match ):
|
||
|
ret = ifind( base_path, file )
|
||
|
if ( not ret[ 0 ] ):
|
||
|
not_found.append( file )
|
||
|
continue
|
||
|
else:
|
||
|
case_table[ path ] = ret[ 1 ]
|
||
|
path = os.path.join( base_path, ret[ 1 ] )
|
||
|
try:
|
||
|
f = open( path )
|
||
|
data = f.read()
|
||
|
f.close()
|
||
|
except:
|
||
|
if ( case_match ):
|
||
|
raise "internal error: ifind success but later read failed"
|
||
|
not_found.append( file )
|
||
|
else:
|
||
|
m = md5.new()
|
||
|
m.update( data )
|
||
|
if ( m.hexdigest() != pak_md5[ file ][ 1 ] ):
|
||
|
print file
|
||
|
updated.append( file )
|
||
|
return ( updated, not_found, case_table )
|
||
|
|
||
|
# find which files are missing in the expanded path, and extract the directories
|
||
|
# returns ( files, dirs, missing )
|
||
|
def status_files_for_path( path, infiles ):
|
||
|
files = []
|
||
|
dirs = []
|
||
|
missing = []
|
||
|
for i in infiles:
|
||
|
test_path = os.path.join( path, i )
|
||
|
if ( os.path.isfile( test_path ) ):
|
||
|
files.append( i )
|
||
|
elif ( os.path.isdir( test_path ) ):
|
||
|
dirs.append( i )
|
||
|
else:
|
||
|
missing.append( i )
|
||
|
return ( files, dirs, missing )
|
||
|
|
||
|
# build a pak from a base path and a list of files
|
||
|
def build_pak( pak, path, files ):
|
||
|
zippy = zipfile.ZipFile( pak, 'w', zipfile.ZIP_DEFLATED )
|
||
|
for i in files:
|
||
|
source_path = os.path.join( path, i )
|
||
|
print source_path
|
||
|
zippy.write( source_path, i )
|
||
|
zippy.close()
|
||
|
|
||
|
# process the list of files after a run to update media
|
||
|
# dds/ -> verify all the .dds are present in zip ( case insensitive )
|
||
|
# .wav -> verify that all .wav have a .ogg version in zip ( case insensitive )
|
||
|
# .tga not in dds/ -> try to find a .dds for them
|
||
|
# work from a list of files, and a path to the base pak files
|
||
|
# files: text files with files line by line
|
||
|
# pak_path: the path to the pak files to compare against
|
||
|
# returns: ( [ missing ], [ bad ] )
|
||
|
# bad are files the function didn't know what to do about ( bug )
|
||
|
# missing are lowercased of all the files that where not matched in build
|
||
|
# the dds/ ones are all forced to .dds extension
|
||
|
# missing .wav are returned in the missing list both as .wav and .ogg
|
||
|
# ( that's handy when you need to fetch next )
|
||
|
def check_files_against_build( files, pak_path ):
|
||
|
pak_list = list_files_in_paks( pak_path )
|
||
|
# make it lowercase
|
||
|
tmp = []
|
||
|
for i in pak_list:
|
||
|
tmp.append( i.lower() )
|
||
|
pak_list = tmp
|
||
|
# read the files and make them lowercase
|
||
|
f = open( files )
|
||
|
check_files = f.readlines()
|
||
|
f.close()
|
||
|
tmp = []
|
||
|
for i in check_files:
|
||
|
s = i.lower()
|
||
|
s = s.replace( '\n', '' )
|
||
|
s = s.replace( '\r', '' )
|
||
|
tmp.append( s )
|
||
|
check_files = tmp
|
||
|
# start processing
|
||
|
bad = []
|
||
|
missing = []
|
||
|
for i in check_files:
|
||
|
if ( i[ :4 ] == 'dds/' ):
|
||
|
if ( i[ len(i)-4: ] == '.tga' ):
|
||
|
i = i[ :-4 ] + '.dds'
|
||
|
elif ( i[ len(i)-4: ] != '.dds' ):
|
||
|
print 'File not understood: ' + i
|
||
|
bad.append( i )
|
||
|
continue
|
||
|
try:
|
||
|
pak_list.index( i )
|
||
|
except:
|
||
|
print 'Not found: ' + i
|
||
|
missing.append( i )
|
||
|
elif ( i[ len(i)-4: ] == '.wav' ):
|
||
|
i = i[ :-4 ] + '.ogg'
|
||
|
try:
|
||
|
pak_list.index( i )
|
||
|
except:
|
||
|
print 'Not found: ' + i
|
||
|
missing.append( i )
|
||
|
missing.append( i[ :-4 ] + '.wav' )
|
||
|
elif ( i[ len(i)-4: ] == '.tga' ):
|
||
|
# tga, not from dds/
|
||
|
try:
|
||
|
pak_list.index( i )
|
||
|
except:
|
||
|
print 'Not found: ' + i
|
||
|
missing.append( i )
|
||
|
i = 'dds/' + i[ :-4 ] + '.dds'
|
||
|
print 'Add dds : ' + i
|
||
|
missing.append( i )
|
||
|
else:
|
||
|
try:
|
||
|
pak_list.index( i )
|
||
|
except:
|
||
|
print 'Not found: ' + i
|
||
|
missing.append( i )
|
||
|
return ( missing, bad )
|
||
|
|
||
|
# match a path to a file in a case insensitive way
|
||
|
# return ( True/False, 'walked up to' )
|
||
|
def ifind( base, path ):
|
||
|
refpath = path
|
||
|
path = os.path.normpath( path )
|
||
|
path = os.path.normcase( path )
|
||
|
# early out just in case
|
||
|
if ( os.path.exists( path ) ):
|
||
|
return ( True, path )
|
||
|
head = path
|
||
|
components = []
|
||
|
while ( len( head ) ):
|
||
|
( head, chunk ) = os.path.split( head )
|
||
|
components.append( chunk )
|
||
|
#print 'head: %s - components: %s' % ( head, repr( components ) )
|
||
|
components.reverse()
|
||
|
level = 0
|
||
|
for root, dirs, files in os.walk( base, topdown = True ):
|
||
|
if ( level < len( components ) - 1 ):
|
||
|
#print 'filter dirs: %s' % repr( dirs )
|
||
|
dirs_del = []
|
||
|
for i in dirs:
|
||
|
if ( not i.lower() == components[ level ].lower() ):
|
||
|
dirs_del.append( i )
|
||
|
for i in dirs_del:
|
||
|
dirs.remove( i )
|
||
|
level += 1
|
||
|
# we assume there is never going to be 2 dirs with only case difference
|
||
|
if ( len( dirs ) != 1 ):
|
||
|
#print '%s: ifind failed dirs matching at %s - dirs: %s' % ( refpath, root, repr( dirs ) )
|
||
|
return ( False, root[ len( base ) + 1: ] )
|
||
|
else:
|
||
|
# must find the file here
|
||
|
for i in files:
|
||
|
if ( i.lower() == components[-1].lower() ):
|
||
|
return ( True, os.path.join( root, i )[ len( base ) + 1: ] )
|
||
|
return ( False, root[ len( base ) + 1: ] )
|
||
|
|
||
|
# do case insensitive FS search on files list
|
||
|
# return [ cased files, not found (unmodified ) ]
|
||
|
def ifind_list( base, files ):
|
||
|
cased = []
|
||
|
notfound = []
|
||
|
for i in files:
|
||
|
ret = ifind( base, i )
|
||
|
if ( ret[ 0 ] ):
|
||
|
cased.append( ret[ 1 ] )
|
||
|
else:
|
||
|
notfound.append( i )
|
||
|
return [ cased, notfound ]
|
||
|
|