#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("", watermark) finalString = finalString.replace("", 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 ") print("Embeds the watermark, using the specified config file\n") print("-image, ") print("Generates image based watermark, using the specified config file\n") print("-verify ") 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()