435 lines
17 KiB
Python
Executable File
435 lines
17 KiB
Python
Executable File
#this file requires python 2.5 or above, NOT version 3 or above, see http://www.python.org/download/ for details
|
|
import os,sys,time,random,hashlib
|
|
|
|
#note, these requires the Python Image Libary, see http://www.pythonware.com/products/pil/
|
|
import Image, ImageDraw, ImageFont, ImageColor
|
|
|
|
#this has to match the values at the top of core/watermark.h
|
|
watermarkDummy = "255,255,255 040,040 0ce168e9add4baa830e2249afca6a63af33b192e1636d4efadb85bbd2aeb4f32c4b083a5d83c3222b052be5bf403ea4a3d34a133c598b36ac14ed50c5d04ea41"
|
|
|
|
imageName = "bokeh_pent"
|
|
imageSize = (512, 256)
|
|
edgeName = "bokeh_deca"
|
|
|
|
pathForFinalDDS = "X:/gta5/art/ng/textures/graphics_pc/"
|
|
|
|
def GetCurrentTimeString():
|
|
return time.strftime("%A, %d %B %Y %H:%M:%S", time.gmtime())
|
|
|
|
def CreateDirectory(path):
|
|
if not os.path.exists(path):
|
|
os.makedirs(path)
|
|
|
|
def ConvertToDDS(pathToFile):
|
|
print("Generating DDS for " + pathToFile)
|
|
os.system(r"nvidia_dds_converter\nvcompress.exe -bc3 " + pathToFile)
|
|
#delete the intermediary
|
|
os.remove(pathToFile)
|
|
|
|
class Watermark:
|
|
name = ""
|
|
colour = ()
|
|
pos = ()
|
|
|
|
def IsValid(this):
|
|
return this.name and len(this.colour) is 3 and len(this.pos) is 2
|
|
|
|
class WatermarkInfo:
|
|
imageWatermarkString = ""
|
|
imageFont = ""
|
|
imageFontSize = -1
|
|
imageBorder = -1
|
|
watermarks = []
|
|
|
|
def ParseFromFile(this, filePath):
|
|
#open and read the content of the config file
|
|
watermarkConfigFile = open(filePath, mode="r")
|
|
|
|
#work out whats on the current line
|
|
currentLine = watermarkConfigFile.readline()
|
|
currentWatermark = Watermark()
|
|
while currentLine:
|
|
#ignore lines that don't contain equals
|
|
if currentLine.find("=") != - 1:
|
|
#ignore any comments
|
|
if currentLine.find("#"):
|
|
currentLine = currentLine.split("#")[0]
|
|
|
|
#get the token and value pair, the strip any whitespace or quotes we arn't interested in
|
|
token, value = currentLine.split("=", 2)
|
|
token = token.strip()
|
|
value = value.strip()
|
|
value = value.strip("\"")
|
|
|
|
#if these match any of the token we process them
|
|
if token == "image_watermark_string":
|
|
this.imageWatermarkString = value
|
|
|
|
elif token == "image_font":
|
|
this.imageFont = value
|
|
|
|
elif token == "image_font_size":
|
|
this.imageFontSize = int(value)
|
|
|
|
elif token == "image_border":
|
|
this.imageBorder = int(value)
|
|
|
|
elif token == "watermark":
|
|
currentWatermark.name = value
|
|
|
|
elif token == "colour":
|
|
#parse colour
|
|
colourSplit = value.split(" ")
|
|
if len(colourSplit) is not 3:
|
|
print(value)
|
|
raise Exception("Error, the config file did not contain a valid colour entry")
|
|
|
|
currentWatermark.colour = (int(colourSplit[0]), int(colourSplit[1]), int(colourSplit[2]))
|
|
|
|
elif token == "pos":
|
|
#parse position
|
|
posSplit = value.split(" ")
|
|
if len(posSplit) is not 2:
|
|
raise Exception("Error, the config file did not contain a valid pos entry")
|
|
|
|
currentWatermark.pos = (int(posSplit[0]), int(posSplit[1]))
|
|
|
|
#if we have a valid entry , then add it and clear the current
|
|
if currentWatermark.IsValid():
|
|
this.watermarks.append(currentWatermark)
|
|
currentWatermark = Watermark()
|
|
|
|
currentLine = watermarkConfigFile.readline()
|
|
watermarkConfigFile.close()
|
|
|
|
if not this.imageWatermarkString:
|
|
raise Exception("Error, the config file did not contain a valid image_watermark_string entry")
|
|
|
|
if not this.imageFont:
|
|
raise Exception("Error, the config file did not contain a valid image_font entry")
|
|
|
|
if this.imageFontSize < 0:
|
|
raise Exception("Error, the config file did not contain a valid image_font_size entry")
|
|
|
|
if this.imageBorder < 0:
|
|
raise Exception("Error, the config file did not contain a valid image_border entry")
|
|
|
|
if len(this.watermarks) == 0:
|
|
raise Exception("Error, the config file did not contain any valid watermark entry")
|
|
|
|
def GetImageWatermakString(this, watermark):
|
|
finalString = this.imageWatermarkString.replace(r"\n", "\n")
|
|
finalString = finalString.replace("<name>", watermark)
|
|
finalString = finalString.replace("<date>", GetCurrentTimeString())
|
|
|
|
return finalString
|
|
|
|
def GenerateWatermark(watermarkString, isDummy = False):
|
|
#create a new hasher using the sha512 algorithm
|
|
hash = hashlib.new('sha512')
|
|
hash.update(watermarkString);
|
|
|
|
if isDummy:
|
|
#if isDummy is set get an easily pasteable dummy value we can use to make sure the buffer is the right size
|
|
#the hex digest is twice the useal size, but the proper version pads itself to be the same size
|
|
random.seed("bertmcbertson")
|
|
secureHash = hash.hexdigest()
|
|
|
|
else:
|
|
#create a hash from the supplied string
|
|
sha512Hash = hash.hexdigest()
|
|
|
|
#seed the random number generator with the hash, plus a magic word
|
|
random.seed(sha512Hash + "malefactor")
|
|
|
|
xorResult = ""
|
|
#got through the hash converting the value from a char to long and then xor it against a long with random bits
|
|
for i in range(len(sha512Hash)):
|
|
xorResult += str(long(ord(sha512Hash[i])) ^ random.getrandbits(32))
|
|
|
|
#update the has with this value and generate a byte digest
|
|
hash.update(xorResult)
|
|
sha512Hash = hash.digest()
|
|
|
|
#reverse the hash
|
|
sha512Hash = sha512Hash[::-1]
|
|
|
|
#create some padding to go around the hash
|
|
hash.update(str(random.getrandbits(32)))
|
|
padding = hash.digest()
|
|
|
|
#create the final string, using the padding
|
|
padPoint = random.randrange(16, 42)
|
|
secureHash = padding[0:padPoint] + sha512Hash + padding[padPoint:64]
|
|
|
|
return secureHash
|
|
|
|
def EmbedWatermark(name, data, watermark, date, outputPath):
|
|
#apply watermark
|
|
finalWatermark = GenerateWatermark(watermark.name + date, False)
|
|
|
|
#add on the colour and pos info
|
|
red = str(watermark.colour[0]).zfill(3)
|
|
green = str(watermark.colour[1]).zfill(3)
|
|
blue = str(watermark.colour[2]).zfill(3)
|
|
|
|
xpos = str(watermark.pos[0]).zfill(3)
|
|
ypos = str(watermark.pos[1]).zfill(3)
|
|
|
|
finalWatermark = red + "," + green + "," + blue + " " + xpos + "," + ypos + " " + finalWatermark;
|
|
|
|
if len(watermarkDummy) != len(finalWatermark):
|
|
print("\"" + watermarkDummy + "\" l:" + str(len(watermarkDummy)))
|
|
print("\"" + finalWatermark + "\" l:" + str(len(finalWatermark)))
|
|
raise Exception("Error, watermark produced is a different length, this won't work!")
|
|
|
|
outputData = data.replace(watermarkDummy, finalWatermark)
|
|
|
|
#write out exec and info
|
|
outputDirectory = outputPath + "/" + watermark.name + "/"
|
|
CreateDirectory(outputDirectory)
|
|
|
|
outputExec = open(outputDirectory + name, mode="wb")
|
|
outputExec.write(outputData)
|
|
outputExec.close()
|
|
|
|
#ouput the info file which contains the watermark indside the exec, this can then be compaired later
|
|
outputInfo = open(outputDirectory + watermark.name + ".wmi", mode="w")
|
|
outputInfo.write("Watermark \"" + watermark.name + "\" was embedded on " + date + ", the watermark is:\n" + finalWatermark)
|
|
outputInfo.close()
|
|
|
|
def GenerateWatermarkedExecs(pathToConfigFile, pathToExec, outputDirectory):
|
|
#read the watermark info file
|
|
print("Reading watermark config file...")
|
|
info = WatermarkInfo()
|
|
info.ParseFromFile(pathToConfigFile)
|
|
|
|
print("Source exec = " + pathToExec + ", output path = " + outputDirectory + ", number of exec to generate = " + str(len(info.watermarks)))
|
|
|
|
#load the specified exec
|
|
execFile = open(pathToExec, mode="rb")
|
|
execData = execFile.read()
|
|
execFile.close()
|
|
|
|
#check the dummy watermark is present
|
|
if execData.find(watermarkDummy) == -1:
|
|
raise Exception("Error, the specified watermark was not found in this exec, check the start value at the top of watermarker.py matches the one in device_win32.cpp")
|
|
|
|
#create new execs based on the watermarks supplied
|
|
for watermark in info.watermarks:
|
|
currentTime = GetCurrentTimeString()
|
|
print("Embeding watermark \"" + watermark.name + "\" on " + currentTime)
|
|
|
|
execNameOnly = pathToExec;
|
|
|
|
#make sure we have only the exec name and not the path as well
|
|
if execNameOnly.find("/"):
|
|
execNameOnly = execNameOnly[execNameOnly.rfind("/") + 1:]
|
|
|
|
if execNameOnly.find("\\"):
|
|
execNameOnly = execNameOnly[execNameOnly.rfind("\\") + 1:]
|
|
|
|
EmbedWatermark(execNameOnly, execData, watermark, GetCurrentTimeString(), outputDirectory)
|
|
|
|
print("Completed!")
|
|
|
|
def DrawText(draw, string, colour, font, startYPos, border):
|
|
currentDrawPosY = startYPos
|
|
textLeftToDraw = string
|
|
stringSplitPoint = len(textLeftToDraw)
|
|
fontHeight = draw.textsize("Font", font = font)[1]
|
|
|
|
longestline = 0
|
|
|
|
while textLeftToDraw != "":
|
|
while draw.textsize(textLeftToDraw[:stringSplitPoint], font = font)[0] > imageSize[0] - border * 2:
|
|
stringSplitPoint -= 1
|
|
|
|
spaceSplit = textLeftToDraw[:stringSplitPoint].rfind(" ")
|
|
|
|
if spaceSplit > 0 and len(textLeftToDraw[:stringSplitPoint]) != len(textLeftToDraw):
|
|
stringSplitPoint = spaceSplit
|
|
|
|
lineLength = draw.textsize(textLeftToDraw[:stringSplitPoint].strip(), font = font)[0]
|
|
if lineLength > longestline:
|
|
longestline = lineLength
|
|
|
|
#invert the colour so we get something that stands out
|
|
strokeColour = (255 - colour[0], 255 - colour[1], 255 - colour[2])
|
|
strokeSize = 1
|
|
|
|
#draw stroke outline
|
|
draw.text((border - strokeSize, border + currentDrawPosY - strokeSize), textLeftToDraw[:stringSplitPoint].strip(), font = font, fill = strokeColour)
|
|
draw.text((border + strokeSize, border + currentDrawPosY - strokeSize), textLeftToDraw[:stringSplitPoint].strip(), font = font, fill = strokeColour)
|
|
draw.text((border - strokeSize, border + currentDrawPosY + strokeSize), textLeftToDraw[:stringSplitPoint].strip(), font = font, fill = strokeColour)
|
|
draw.text((border + strokeSize, border + currentDrawPosY + strokeSize), textLeftToDraw[:stringSplitPoint].strip(), font = font, fill = strokeColour)
|
|
|
|
#draw the main part of the text
|
|
draw.text((border, border + currentDrawPosY), textLeftToDraw[:stringSplitPoint].strip(), font = font, fill = colour)
|
|
|
|
textLeftToDraw = textLeftToDraw[stringSplitPoint:].strip()
|
|
stringSplitPoint = len(textLeftToDraw)
|
|
currentDrawPosY += fontHeight
|
|
|
|
return currentDrawPosY, longestline
|
|
|
|
def GenerateWatermarkImages(pathToConfigFile, outputDirectory):
|
|
#read the watermark info file
|
|
print("Reading watermark config file...")
|
|
info = WatermarkInfo()
|
|
info.ParseFromFile(pathToConfigFile)
|
|
|
|
print("output path = " + outputDirectory + ", number of images to generate = " + str(len(info.watermarks)) * 2)
|
|
|
|
#assumes windows is on c:
|
|
font = ImageFont.truetype("c:/windows/fonts/" + info.imageFont + ".ttf", info.imageFontSize)
|
|
|
|
#loop through the watermarks and draw an image for each one
|
|
for watermark in info.watermarks:
|
|
print("Creating image watermark \"" + watermark.name + "\" on " + GetCurrentTimeString())
|
|
outputDirectory = outputDirectory + "/" + watermark.name + "/"
|
|
CreateDirectory(outputDirectory)
|
|
|
|
#create a new image
|
|
image = Image.new("RGBA", imageSize, (0,0,0,0))
|
|
draw = ImageDraw.Draw(image)
|
|
yPos = 0
|
|
width = 0
|
|
|
|
#render the text
|
|
stringToDraw = info.GetImageWatermakString(watermark.name)
|
|
lines = stringToDraw.split('\n')
|
|
for line in lines:
|
|
yPos, longestline = DrawText(draw, line, watermark.colour, font, yPos, info.imageBorder)
|
|
if longestline > width:
|
|
width = longestline
|
|
|
|
#clear out any area of the texture that isn't border or string
|
|
draw.rectangle([0, info.imageBorder * 2 + yPos, imageSize[0], imageSize[1]], fill = (0,0,0,0))
|
|
draw.rectangle([width + info.imageBorder * 2, 0, imageSize[0], imageSize[1]], fill = (0,0,0,0))
|
|
|
|
image.save(pathForFinalDDS + imageName + ".tga","TGA")
|
|
|
|
#use the nVidia dds convertor to spit out a DDS file
|
|
ConvertToDDS(pathForFinalDDS + imageName + ".tga")
|
|
|
|
#genereate the secret watermark
|
|
GenerateEdgeWatermark(watermark.name, outputDirectory + "secret_ref.tga", False)
|
|
GenerateEdgeWatermark(watermark.name, pathForFinalDDS + edgeName + ".tga", True)
|
|
|
|
print("Complete!")
|
|
|
|
|
|
#sigh, python 2.5 doesn't have a in build inbinary convertor, don't want to upgrade in case the build machines don't have it :/
|
|
def ToBinaryString(number):
|
|
out = []
|
|
if number == 0: out.append('0')
|
|
while number > 0:
|
|
out.append('01'[number & 1])
|
|
number >>= 1
|
|
|
|
return ''.join(reversed(out))
|
|
|
|
|
|
def GenerateEdgeWatermark(string, outputPath, final):
|
|
#if final make the iimage more tranparent so it blends into the scene
|
|
if final:
|
|
image = Image.new("RGBA", (128, 4), (0, 0, 0, 0))
|
|
else:
|
|
image = Image.new("RGBA", (128, 4), (255, 255, 255, 255))
|
|
|
|
draw = ImageDraw.Draw(image)
|
|
|
|
xPos = 0;
|
|
|
|
if final:
|
|
color = (128,128,128,70)
|
|
else:
|
|
color = (0,0,0,255)
|
|
|
|
#draw a double height line for the start
|
|
draw.line([0, 0, 0, 1], fill = color)
|
|
xPos += 1
|
|
|
|
#output the string as binary pixel sequence
|
|
for letter in string:
|
|
letterAsBinary = ToBinaryString(ord(letter))
|
|
for bin in letterAsBinary:
|
|
if bin == "0":
|
|
draw.line([xPos, 0, xPos, 0], fill = color)
|
|
xPos += 1
|
|
|
|
#draw a double line for the end
|
|
draw.line([xPos, 0, xPos, 1], fill = color)
|
|
|
|
image.save(outputPath,"TGA")
|
|
|
|
#use the nVidia dds convertor to spit out a DDS file
|
|
if final:
|
|
ConvertToDDS(outputPath)
|
|
|
|
def SearchForWatermarkInfo(execData, dirname, filenames):
|
|
foundWatermark = False
|
|
print("Seaching " + dirname +"..")
|
|
for file in filenames:
|
|
#load the watermark info file in
|
|
if file.find(".wmi") is not -1:
|
|
watermarkInfo = open(dirname + "/" + file, mode="r")
|
|
watermarkInfo.readline() #the first line is a text discription of the watermark
|
|
watermark = watermarkInfo.read()
|
|
watermarkInfo.close()
|
|
|
|
#check to see if it contains the watermark we're looking for
|
|
if execData.find(watermark) != -1:
|
|
print("\tWatermark file, " + file + " is a MATCH for the watermark found in the supplied exec")
|
|
foundWatermark = True
|
|
|
|
if foundWatermark == False:
|
|
print("\tCouldn't find any watermarks which matched the supplied exec")
|
|
|
|
def VerifyWatermark(pathToSourceExec, pathToSearch):
|
|
print("Exec to check = " + pathToSourceExec)
|
|
|
|
#load the specified exec
|
|
execFile = open(pathToSourceExec, mode="rb")
|
|
execData = execFile.read()
|
|
execFile.close()
|
|
|
|
#look through the watermark info directory to find a match
|
|
os.path.walk(pathToSearch, SearchForWatermarkInfo, execData)
|
|
|
|
def GenerateDummy():
|
|
#genrate a dummy value that is then replaced by the real watermark
|
|
print("Dummy Value:")
|
|
print("\n" + GenerateWatermark("Watermark" + GetCurrentTimeString(), True) + "\n")
|
|
|
|
def Watermarker():
|
|
params = sys.argv[1:]
|
|
if len(params) > 0:
|
|
if params[0] == "-embed":
|
|
GenerateWatermarkedExecs(params[1], params[2], params[3])
|
|
sys.exit()
|
|
elif params[0] == "-image":
|
|
GenerateWatermarkImages(params[1], params[2])
|
|
sys.exit()
|
|
elif params[0] == "-verify":
|
|
VerifyWatermark(params[1], params[2])
|
|
sys.exit()
|
|
elif params[0] == "-codedummy":
|
|
GenerateDummy()
|
|
sys.exit()
|
|
|
|
|
|
print("Help:")
|
|
print("-embed <config file> <source exec> <output path>")
|
|
print("Embeds the watermark, using the specified config file\n")
|
|
print("-image, <watermarks config file> <output path>")
|
|
print("Generates image based watermark, using the specified config file\n")
|
|
print("-verify <source exec> <search path>")
|
|
print("verify the watermark against the generate watermark info files in the search path\n")
|
|
print("-codedummy")
|
|
print("Generates a dummy string that can be use to locate the string in the exec and to make sure the array is the same size as the actual watermark.")
|
|
|
|
|
|
Watermarker()
|