text:
fileFormatVersion: 2
guid: a6b713f0d548447e2a0103d2047a304a
ScriptedImporter:
  internalIDToNameTable: []
  externalObjects: {}
  serializedVersion: 2
  userData:
  assetBundleName:
  assetBundleVariant:
  script: {fileID: 11500000, guid: 60072b568d64c40a485e0fc55012dc9f, type: 3}


text:
#include "Packages/jp.keijiro.noiseshader/Shader/ClassicNoise2D.hlsl"
#include "Packages/jp.keijiro.noiseshader/Shader/ClassicNoise3D.hlsl"
#include "Packages/jp.keijiro.noiseshader/Shader/SimplexNoise2D.hlsl"
#include "Packages/jp.keijiro.noiseshader/Shader/SimplexNoise3D.hlsl"

void ClassicNoise2D_float(float2 pos, out float value)
{
    value = ClassicNoise(pos);
}

void PeriodicNoise2D_float(float2 pos, float2 rep, out float value)
{
    value = PeriodicNoise(pos, rep);
}

void SimplexNoise2D_float(float2 pos, out float value, out float2 grad)
{
    float3 n = SimplexNoiseGrad(pos);
    grad = n.xy;
    value = n.z;
}

void ClassicNoise3D_float(float3 pos, out float value)
{
    value = ClassicNoise(pos);
}

void PeriodicNoise3D_float(float3 pos, float3 rep, out float value)
{
    value = PeriodicNoise(pos, rep);
}

void SimplexNoise3D_float(float3 pos, out float value, out float3 grad)
{
    float4 n = SimplexNoiseGrad(pos);
    grad = n.xyz;
    value = n.w;
}

repo_name: keijiro/ShaderGraphAssets

repo_language: HLSL

file_name: Simplex Noise 3D.shadersubgraph.meta

mime_type: text/plain


text:
fileFormatVersion: 2
guid: 56d12a092082fc0429ca80d73146bcce
ScriptedImporter:
  internalIDToNameTable: []
  externalObjects: {}
  serializedVersion: 2
  userData:
  assetBundleName:
  assetBundleVariant:
  script: {fileID: 11500000, guid: 60072b568d64c40a485e0fc55012dc9f, type: 3}


text:
fileFormatVersion: 2
guid: 1535c1014b06060448f423bc0dafe33f
ScriptedImporter:
  internalIDToNameTable: []
  externalObjects: {}
  serializedVersion: 2
  userData:
  assetBundleName:
  assetBundleVariant:
  script: {fileID: 11500000, guid: 60072b568d64c40a485e0fc55012dc9f, type: 3}


text:
fileFormatVersion: 2
guid: 393273d272f07434fb03477bbf01acb5
ScriptedImporter:
  internalIDToNameTable: []
  externalObjects: {}
  serializedVersion: 2
  userData:
  assetBundleName:
  assetBundleVariant:
  script: {fileID: 11500000, guid: 60072b568d64c40a485e0fc55012dc9f, type: 3}


text:
fileFormatVersion: 2
guid: c8c8affd191994d45bd319ca1b405453
ScriptedImporter:
  internalIDToNameTable: []
  externalObjects: {}
  serializedVersion: 2
  userData:
  assetBundleName:
  assetBundleVariant:
  script: {fileID: 11500000, guid: 60072b568d64c40a485e0fc55012dc9f, type: 3}


text:
fileFormatVersion: 2
guid: 9c92377ca28c42346b429a1dcd940a83
ScriptedImporter:
  internalIDToNameTable: []
  externalObjects: {}
  serializedVersion: 2
  userData:
  assetBundleName:
  assetBundleVariant:
  script: {fileID: 11500000, guid: 60072b568d64c40a485e0fc55012dc9f, type: 3}


text:
fileFormatVersion: 2
guid: 284502af4c2ec0a49ac6d4579447cbbd
ScriptedImporter:
  internalIDToNameTable: []
  externalObjects: {}
  serializedVersion: 2
  userData:
  assetBundleName:
  assetBundleVariant:
  script: {fileID: 11500000, guid: 60072b568d64c40a485e0fc55012dc9f, type: 3}


text:
float RadialSpeedlines
  (float2 UV, float Freq, float2 Thick, float2 Radius, float Seed)
{
    float2 npos = (UV - 0.5) * 2;

    // Polar coordinates
    float2 polar;
    polar.x = (atan2(npos.x, npos.y) / PI + 1) / 2;
    polar.y = length(npos);

    // Spoke number / parameter in spoke
    uint spoke = (uint)((polar.x + Seed) * Freq) * 4;
    float param = frac(polar.x * Freq) * 2 - 1;

    // Random number generator
    float r1 = Hash(spoke) * 2 - 1;
    float r2 = Hash(spoke + 1);
    float r3 = Hash(spoke + 2);

    // Spoke offset / width
    float offs = Thick.y * r1 * 0.95;
    float width = Thick.x * (1 - abs(offs)) * (1 - Thick.y * r2);

    // Extent toward outer ring
    float inner_ring = Radius.x + Radius.y * r3;
    width *= smoothstep(inner_ring, inner_ring + 1, polar.y);

    // Thresholding with pseudo antialiasing
    // Equivalent to [ abs(param + offs) <= width ] but with AA.
    float coeff = 1000;
    float pot = width * (1 + Freq / coeff) - abs(param + offs);
    float level = saturate(pot * coeff / Freq);

    return level;
}

void RadialSpeedlines_float
  (float2 UV, float Freq, float2 Thick, float2 Radius, float Seed, out float Out)
{
    Out = RadialSpeedlines(UV, Freq, Thick, Radius, Seed);
}

void RadialSpeedlinesX4_float
  (float2 UV, float Freq, float2 Thick, float2 Radius, float Seed, out float Out)
{
    float2 uvdx = ddx(UV) / 4;
    float2 uvdy = ddy(UV) / 4;

    float acc = 0;
    acc += RadialSpeedlines(UV              , Freq, Thick, Radius, Seed) * 2;
    acc += RadialSpeedlines(UV - uvdx - uvdy, Freq, Thick, Radius, Seed);
    acc += RadialSpeedlines(UV + uvdx - uvdy, Freq, Thick, Radius, Seed);
    acc += RadialSpeedlines(UV - uvdx + uvdy, Freq, Thick, Radius, Seed);
    acc += RadialSpeedlines(UV + uvdx + uvdy, Freq, Thick, Radius, Seed);
    Out = acc / 6;
}


text:
fileFormatVersion: 2
guid: 031d1f025754a496bb1a8a76a926c81c
ScriptedImporter:
  internalIDToNameTable: []
  externalObjects: {}
  serializedVersion: 2
  userData:
  assetBundleName:
  assetBundleVariant:
  script: {fileID: 11500000, guid: 60072b568d64c40a485e0fc55012dc9f, type: 3}


text:
fileFormatVersion: 2
guid: 697ce61debf102044a768f4f75a813ea
ScriptedImporter:
  internalIDToNameTable: []
  externalObjects: {}
  serializedVersion: 2
  userData:
  assetBundleName:
  assetBundleVariant:
  script: {fileID: 11500000, guid: 60072b568d64c40a485e0fc55012dc9f, type: 3}


text:
fileFormatVersion: 2
guid: 1b32d39e67254cf4287f4a389b08d215
ScriptedImporter:
  internalIDToNameTable: []
  externalObjects: {}
  serializedVersion: 2
  userData:
  assetBundleName:
  assetBundleVariant:
  script: {fileID: 11500000, guid: 60072b568d64c40a485e0fc55012dc9f, type: 3}


text:
fileFormatVersion: 2
guid: 8eaa9acd71b7f254f83fb31cde207cce
TextureImporter:
  internalIDToNameTable: []
  externalObjects: {}
  serializedVersion: 11
  mipmaps:
    mipMapMode: 0
    enableMipMap: 0
    sRGBTexture: 1
    linearTexture: 0
    fadeOut: 0
    borderMipMap: 0
    mipMapsPreserveCoverage: 0
    alphaTestReferenceValue: 0.5
    mipMapFadeDistanceStart: 1
    mipMapFadeDistanceEnd: 3
  bumpmap:
    convertToNormalMap: 0
    externalNormalMap: 0
    heightScale: 0.25
    normalMapFilter: 0
  isReadable: 0
  streamingMipmaps: 0
  streamingMipmapsPriority: 0
  vTOnly: 0
  ignoreMasterTextureLimit: 0
  grayScaleToAlpha: 0
  generateCubemap: 6
  cubemapConvolution: 0
  seamlessCubemap: 0
  textureFormat: 1
  maxTextureSize: 2048
  textureSettings:
    serializedVersion: 2
    filterMode: 1
    aniso: 1
    mipBias: 0
    wrapU: 1
    wrapV: 1
    wrapW: 1
  nPOTScale: 1
  lightmap: 0
  compressionQuality: 50
  spriteMode: 0
  spriteExtrude: 1
  spriteMeshType: 1
  alignment: 0
  spritePivot: {x: 0.5, y: 0.5}
  spritePixelsToUnits: 100
  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
  spriteGenerateFallbackPhysicsShape: 1
  alphaUsage: 0
  alphaIsTransparency: 0
  spriteTessellationDetail: -1
  textureType: 0
  textureShape: 1
  singleChannelComponent: 0
  flipbookRows: 1
  flipbookColumns: 1
  maxTextureSizeSet: 0
  compressionQualitySet: 0
  textureFormatSet: 0
  ignorePngGamma: 0
  applyGammaDecoding: 0
  platformSettings:
  - serializedVersion: 3
    buildTarget: DefaultTexturePlatform
    maxTextureSize: 2048
    resizeAlgorithm: 0
    textureFormat: 63
    textureCompression: 1
    compressionQuality: 50
    crunchedCompression: 0
    allowsAlphaSplitting: 0
    overridden: 0
    androidETC2FallbackOverride: 0
    forceMaximumCompressionQuality_BC6H_BC7: 0
  - serializedVersion: 3
    buildTarget: Standalone
    maxTextureSize: 2048
    resizeAlgorithm: 0
    textureFormat: -1
    textureCompression: 1
    compressionQuality: 50
    crunchedCompression: 0
    allowsAlphaSplitting: 0
    overridden: 0
    androidETC2FallbackOverride: 0
    forceMaximumCompressionQuality_BC6H_BC7: 0
  - serializedVersion: 3
    buildTarget: Server
    maxTextureSize: 2048
    resizeAlgorithm: 0
    textureFormat: -1
    textureCompression: 1
    compressionQuality: 50
    crunchedCompression: 0
    allowsAlphaSplitting: 0
    overridden: 0
    androidETC2FallbackOverride: 0
    forceMaximumCompressionQuality_BC6H_BC7: 0
  spriteSheet:
    serializedVersion: 2
    sprites: []
    outline: []
    physicsShape: []
    bones: []
    spriteID:
    internalID: 0
    vertices: []
    indices:
    edges: []
    weights: []
    secondaryTextures: []
    nameFileIdTable: {}
  spritePackingTag:
  pSDRemoveMatte: 0
  pSDShowRemoveMatteOption: 0
  userData:
  assetBundleName:
  assetBundleVariant:


text:
fileFormatVersion: 2
guid: f1bfbf5a25830814fa8ee4078ced309b
ScriptedImporter:
  internalIDToNameTable: []
  externalObjects: {}
  serializedVersion: 2
  userData:
  assetBundleName:
  assetBundleVariant:
  script: {fileID: 11500000, guid: 60072b568d64c40a485e0fc55012dc9f, type: 3}


text:
#!/usr/bin/python3
# coding: utf-8

# https://docs.python.org/3.0/library/urllib.request.html
# webpage type : https://docs.python.org/3.4/library/http.client.html
import os, sys, json, re, argparse, urllib.request, html5lib
from bs4 import BeautifulSoup, Tag, UnicodeDammit

aliens_url = 'http://www.imsdb.com/scripts/Aliens.html'
matrix_url = 'http://www.imsdb.com/scripts/Matrix,-The.html'
wrong_url = 'http://www.imqsdsdb.com/scriptljhs/Maqsdtrix,-The.html'

helptext="""Welcome to the Movie Script Parser!

It aims at creating a well formatted JSON file from the URL of a movie script online.

The parser was written using only scripts from http://www.imsdb.com, so it might fail on other sites (and also fail on IMSDB, who knows?).

It was written by Adrien Luxey for Pierre Peigné-Leroy in 2016."""

argparser= argparse.ArgumentParser(description=helptext)
argparser.add_argument('script_url', metavar='script_url', type=str, nargs='?',
        help='URL of the webpage containing the movie script')

args=argparser.parse_args()

# loop until we get a valid script_url

script_url = ''
is_webpage_fetched = False
while not is_webpage_fetched:
    # get the script's URL from the parameters if it was passed
    if( script_url == '' and args.script_url != None ):
        script_url = args.script_url
    else:
        print('Please provide the URL of a movie script you want to see parsed as JSON.')
        print('The parser was intended to work with imsdb.com, and you must provide a full URL (with http[s]://)')

        script_url = input('--> ')

    try:
        request = urllib.request.Request(script_url)
        webpage_bytes = urllib.request.urlopen(request)
        soup = BeautifulSoup(webpage_bytes, 'lxml')
        print('Detected encoding is ', soup.original_encoding)
        is_webpage_fetched = True
    except urllib.error.URLError as err:
        print('Catched an URLError while fetching the URL:', err)
        print()
        pass
    except ValueError as err:
        print('Catched a ValueError while fetching the URL:', err)
        print()
        pass
    except:
        print('Catched an unrecognized error')
        raise
    else:
        #script_text = soup.find("td", class_="scrtext").find("pre")
        script_text = soup.find("pre")

        if( script_text.find("pre") ):
            print('Found a <pre> inside the <pre>')
            script_text = script_text.find("pre")

        print('Parsing {} and extracting the first <pre> resulted in the following text:'.format(request.full_url))
        print(str(script_text)[:256])
        answer = input('Is that the script you expected? (Y/n) ')

        if( answer == 'N' or answer == 'n' ):
            answer = input('Shall we try with another URL? (Y/n) ')
            if( answer == 'N' or answer == 'n' ):
                raise ValueError('The result was not what we expected.')

        is_webpage_fetched = True

print()
print()
print('OK, we have the text. A few questions before we get parsing:')

# script dict to be serialized as JSON
script=dict()

# Let's fill what we can here
script['movie_url'] = request.full_url

# movie's name
answer = 'n'
while (answer == 'n' or answer == 'N'):
    script['movie_title'] = input("What's the name of this movie? ")
    answer = input("You typed '{}', is that right? (Y/n)".format(script['movie_title']))

# movie's year
answer = 'n'
while (answer == 'n' or answer == 'N'):
    script['movie_year'] = input("When was this movie released? (Leave blank to skip) ")
    if(script['movie_year'] == ''):
        answer = input("You didn't type anything, skip? (Y/n)")
        if(answer != 'n' and answer != 'N'):
            del script['movie_year']
    else:
        answer = input("You typed '{}', is that right? (Y/n)".format(script['movie_year']))

BLOCK_TYPES=['character', 'speech', 'stage direction', 'location']
CHARACTER=0
SPEECH=1
DIRECTIONS=2
LOCATION=3

# COMPILE ALL THE REGULAR EXPRESSIONS!
spaces_regex = re.compile("^(\s*).*")
location_regex = re.compile("^\s*(INT\.|EXT\.)")

def get_line_type(line, stripped_line, usual_spaces, characters):
    # Counting the number of spaces at the beginning of the line
    spmatch = spaces_regex.search(line)
    spaces_number = len(spmatch.group(1))
    block_type = 0

    if( location_regex.search(line) != None ):
        return LOCATION

    if stripped_line in characters:
        return CHARACTER

    # Look for space
    for block_type_usual_spaces in usual_spaces:
        if spaces_number in block_type_usual_spaces:
            block_type = usual_spaces.index(block_type_usual_spaces)
            #print('We consider {:d} leading spaces as a \'{:s}\' block.'.format(
            #      spaces_number, BLOCK_TYPES[block_type]))
            return usual_spaces.index(block_type_usual_spaces)

    print('There are {:d} space(s) at the beginning of this line'.format(spaces_number))
    question = "What kind of block is that?\n"
    for i in range(len(BLOCK_TYPES)):
        question += '\t('+str(i)+') ' + BLOCK_TYPES[i] + '\n'
    print(question)

    validated = False
    while( validated == False):
        try:
            block_type = int(input('? [0-{:d}] '.format(len(BLOCK_TYPES)-1)))
            while( block_type < 0 or block_type >= len(BLOCK_TYPES)):
                block_type = int(input('? [0-{:d}] '.format(len(BLOCK_TYPES)-1)))
        except ValueError:
            continue

        validated = True
        answer = input('You said the last block type was \'{:s}\', sure about that? (Y/n) '.format(
                BLOCK_TYPES[block_type]))
        if( answer == 'n' or answer =='N' ):
            validated = False

    remember_spaces = False
    validated = False
    while( validated == False):
        answer_spaces = input('Are all  lines with {:d} leading spaces \'{:s}\' blocks ? (Y/n) '.format(
                spaces_number, BLOCK_TYPES[block_type]))

        if( answer_spaces == 'n' or answer_spaces =='N' ):
            print('You said no: we will ask you again next time.')
            remember_spaces = False
        else:
            print('You said yes: ' +
                  'every new block with {:d} leading spaces '.format(spaces_number) +
                  'will now be considered a \'{:s}\'.'.format(BLOCK_TYPES[block_type]) )
            remember_spaces = True

        validated = True
        answer = input('Are you sure? (Y/n) ')
        if( answer == 'n' or answer =='N' ):
            validated = False

    if( remember_spaces ):
        usual_spaces[block_type].append(spaces_number)

    return block_type

# In[53]:

# DA big loop

usual_spaces=[[] for i in range(len(BLOCK_TYPES))]

# Ici on définit les variables qu'on remplira de texte
is_intro = True
movie_script = []
intro = []
last_line_type = -1
last_character = ''
text = []
characters=[]

print()
print()
print("Here we go for some kickass movie script parsing!")
print()
print()
print("Start by telling me when the introduction will end.")

for block in script_text.descendants:
    # Si block est une instance de bs4.Tag, il est entouré de balises HTML
    # Le prochain block contiendra le même texte sans les balises
    # Donc on continue sans parser ce bloc
    if(isinstance(block, Tag)):
        continue

    # UnicodeDammit converts any string to UTF-8
    # does not work so well
    block = UnicodeDammit(block, soup.original_encoding).unicode_markup
    # remove leading and ending end of lines
    block = block.strip('\n')

    # if the block doesn't have any text, skip it
    if( re.search('\w', block) == None ):
        continue

    # bs4 ne coupe pas toujours bien les différents blocs
    # Mieux vaut donc redécouper par paragraphe et les traiter un à un
    for line in block.split('\n'):
        stripped_line = line.strip(' \n\t\r')
        if( re.search('\w', line) == None ):
            continue

        print('------------------------------ Begin line ------------------------------')
        print(line)
        print('                        ------- End line -------')

        if( is_intro ):
            print()
            answer = input("Is that still part of the intro? (Y/n) ")

            if(answer == 'n' or answer == 'N'):
                is_intro = False
                movie_script.append({
                    'type': 'introduction',
                    'text': '\n'.join(intro)})

                print(movie_script[-1])
            else:
                print("OK")
                print()
                intro.append(stripped_line)
                continue

        line_type = get_line_type(line, stripped_line, usual_spaces, characters)
        print("The last line was interpreted as '{}'".format(BLOCK_TYPES[line_type]))
        print()

        if(last_line_type == -1 # -1 = not initialized
           or last_line_type == line_type):
            text.append(stripped_line)
        else:
            if(last_line_type == CHARACTER):
                last_character='\n'.join(text)
                if not last_character in characters:
                    characters.append(last_character)
            elif(last_line_type == SPEECH):
                movie_script.append({
                    'type': BLOCK_TYPES[last_line_type],
                    BLOCK_TYPES[CHARACTER]: last_character,
                    'text': '\n'.join(text)})
                print('We just parsed this JSON block:')
                print(movie_script[-1])
            else:
                movie_script.append({
                    'type': BLOCK_TYPES[last_line_type],
                    'text': '\n'.join(text)})
                print('We just parsed this JSON block:')
                print(movie_script[-1])
            text=[stripped_line]

        last_line_type = line_type

        #print('block_type={:d}'.format(line_type))
        #print('usual spaces:')
        #print(usual_spaces)
        #print('This line is a \'{:s}\'.'.format(BLOCK_TYPES[line_type]))

        print()

    print()
    print()

movie_script.append({
    'type': BLOCK_TYPES[line_type],
    'text': '\n'.join(text)})

print('We just parsed this JSON block:')
print(movie_script[-1])
print()
print()

script['movie_script'] = movie_script

print('All done, biiiiitch!')

# In[64]:

print(flush=True)
print(flush=True)
print('(Our current directory is: {})'.format(os.getcwd()), flush=True)
out_filename = input('Now, gimme an output filename: ')

try:
    fd = open(out_filename, 'w')
    json.dump(script, fd, indent=True)
    print('We just successfully wrote {}\'s script as JSON to {} .'.format(script['movie_title'], fd.name))
    print('Bravo!')
except:
    print("Shit happened: ", sys.exc_info()[0])
finally:
    fd.close()
    print()
    print('This script was made by Adrien Luxey for Pierre Peigné-Leroy in 2016.')
    print('It\'s free to use and all, go check our licence.')

repo_language: Python


text:
#!/usr/bin/python3
# coding: utf-8

import os, sys, json, argparse

def input_from_list(question, choices):
    '''
    Asks the user to choose among a list (choices) given a question
    '''
    action = 0

    question += '\n'
    for i in range(len(choices)):
        question += '\t('+str(i)+') ' + choices[i] + '\n'
    print(question)

    validated = False
    while( validated == False ):
        try:
            action = int(input('? [0-{:d}] '.format(len(choices)-1)))
            while( action < 0 or action >= len(choices)):
                action = int(input('? [0-{:d}] '.format(len(choices)-1)))
        except ValueError:
            continue

        validated = True
        answer = input('You chose \'{:s}\', sure about that? (Y/n) '.format(
                choices[action]))
        if( answer == 'n' or answer =='N' ):
            validated = False

    return action

def input_string(question):
    '''
    Asks the user to type some input given a question
    '''
    user_input = ''
    answer = 'n'
    while (answer == 'n' or answer == 'N'):
        user_input = input(question + " (Leave blank to abort) ")
        if(user_input == ''):
            answer = input("You didn't type anything, abort? (Y/n)")
            if(answer != 'n' and answer != 'N'):
                print('You\'re loosing my time, you know?')
                break
        else:
            answer = input("You typed '{}', is that right? (Y/n)".format(user_input))

    return user_input

def extract_characters(script):
    '''
    Extracts the (unique) characters list from the script
    '''
    characters=[]
    for block in script['movie_script']:
        if(block['type'] == BLOCK_TYPES[SPEECH]):
            character = block['character']
            if not character in characters:
                characters.append(character)

    return characters

def extract_locations(script):
    '''
    Extracts the (unique) locations list from the script
    '''
    locations=[]
    for block in script['movie_script']:
        if(block['type'] == BLOCK_TYPES[LOCATION]):
            location = block['text']
            if not location in locations:
                locations.append(location)

    return locations

def extract_directions(script):
    '''
    Extracts the stage directions list from the script
    '''
    directions=[]
    for block in script['movie_script']:
        if(block['type'] == BLOCK_TYPES[DIRECTIONS]):
            directions.append(block['text'])

    return directions

def extract_speech_given_character(script, character, strict_match=False):
    '''
    Extracts the given character's utterances from the script

    If strict_match is True, we will only extract utterances that perfectly match (==) the parameter;
    otherwise, we will extract utterances whose character partly matches (in) the parameter.
    In both cases, the match is case-insensitive.

    Also asks the user wether one wants to keep the character's name before each utterance.
    '''

    keep_character_name = False
    answer = input('Do you want to keep the character\'s names? (y/N) ')
    if( answer == 'y' or answer =='Y' ):
        keep_character_name = True

    speeches=[]
    for block in script['movie_script']:
        if( block['type'] == BLOCK_TYPES[SPEECH] and
            (strict_match and (character.lower() == block['character'].lower()) or
             not strict_match and (character.lower() in block['character'].lower())) ):
            if( keep_character_name ):
                speeches.append(block['character'])
            speeches.append(block['text'])

    return speeches

def extract_all_characters_speech(script):
    '''
    Extracts all speeches from the script
    '''
    return extract_speech_given_character(script, '')

def extract_speech_asking_user(script):
    '''
    Extracts utterances by asking the user which character one wants to get

    Also asks wether the user wants a perfect (==) or partial (in) match.
    '''
    character = input_string('Please provide the name of the character: ')

    strict_match = False
    answer = input('Do you want utterances of this exact character (or any character that matches \'{}\')? (y/N) '.format(character))
    if( answer == 'y' or answer =='Y' ):
        strict_match = True

    return extract_speech_given_character(script, character, strict_match)

def extract_speech_using_characters_list(script):
    '''
    Extracts utterances by providing the user with the characters list

    Also asks wether the user wants a perfect (==) or partial (in) match.
    '''
    characters = sorted(extract_characters(script))
    character = characters[input_from_list('Please choose a character:', characters)]

    strict_match = False
    answer = input('Do you want utterances of this exact character (or any character that matches \'{}\')? (y/N) '.format(character))
    if( answer == 'y' or answer =='Y' ):
        strict_match = True

    return extract_speech_given_character(script, character, strict_match)

def extract_speech(script):
    '''
    Asks the user which speeches one wants to extract, and calls the appropriate function
    '''
    speech=[]

    choices = ['all characters',
               'give the character\'s name',
               'choose from the characters list']

    action = input_from_list("Which character speeches do you want to extract?", choices)

    if( action == 0 ):
        return extract_all_characters_speech(script)
    elif( action == 1 ):
        return extract_speech_asking_user(script)
    elif( action == 2 ):
        return extract_speech_using_characters_list(script)

helptext="""Welcome to the JSON Movie Script Querier!

With it, you can query a JSON file created with movie_script_parse.py to extract only the information you need.

It was written by Adrien Luxey for Pierre Peigné-Leroy in 2016."""

BLOCK_TYPES=['character', 'speech', 'stage direction', 'location']
CHARACTER=0
SPEECH=1
DIRECTIONS=2
LOCATION=3

ACTIONS=['extract all character names', 'extract some speech',
         'extract all stage directions', 'extract all locations']
EXTRACT_CHARACTERS=0
EXTRACT_SPEECH=1
EXTRACT_DIRECTIONS=2
EXTRACT_LOCATIONS=3

argparser= argparse.ArgumentParser(description=helptext)
argparser.add_argument('json_filename', metavar='json_filename', type=str, nargs='?',
        help='Location of the JSON file on your machine')
argparser.add_argument('out_filename', metavar='out_filename', type=str, nargs='?',
                       help='Location of the output file you want to make')

args=argparser.parse_args()

# loop until we get a valid json_filename

#json_filename="home/adrien/Programmation/Pierre/da-funky-movie-script-parser/outputs/the_fifth_element.json"
json_filename = ''
if( args.json_filename != None ):
    json_filename = args.json_filename
else:
    json_filename = input_string('Please provide the movie\'s JSON file path: ')

with open(json_filename) as fd:
    script = json.load(fd)

    print()
    print('You opened the movie \'{}\'.'.format(script['movie_title']))
    print()

    action = input_from_list("What do you want to do with it? ", ACTIONS)

    if( action == EXTRACT_CHARACTERS ):
        result_list = extract_characters(script)
    elif( action == EXTRACT_SPEECH ):
        result_list = extract_speech(script)
    elif( action == EXTRACT_DIRECTIONS ):
        result_list = extract_directions(script)
    else: # EXTRACT_LOCATIONS
        result_list = extract_locations(script)

    result_string = '\n\n'.join(result_list)

    print(result_string)

    print()
    print()
    print()
    input('Happy? (Y/I don\'t really care) ')
    print()

    if( args.out_filename != None ):
        out_filename = args.out_filename
    else:
        print('(We\'re in {})'.format(os.getcwd()))
        out_filename = input_string('Where do you want to save that?')

    try:
        fd = open(out_filename, 'w')
        fd.write(result_string)
        print('We just successfully wrote what you asked to {} .'.format(script['movie_title'], fd.name))
        print('Bravo!')
    except:
        print("Shit happened: ", sys.exc_info()[0])
    finally:
        fd.close()
        print()
        print('This script was made by Adrien Luxey for Pierre Peigné-Leroy in 2016.')
        print('It\'s free to use and all, go check our licence.')


text:
debian: libxml2-dev, libxslt1-dev
python3 packages: lxml, bs4


text:
# Da Fonky Movie Script Parser

## What is it for?

To parse movie scripts found online (in HTML) as JSON.

Indeed, movie scripts are always displayed in a certain format, but not in a way that would be intelligible for a computer program for later studies (stylometry, for instance).

The goal of this program is to create a JSON file from your movie script, differentiating the following types of text: character, speech, stage direction, and location.

Here are some movie scripts databases: http://www.imsdb.com , http://www.script-o-rama.com/table.shtml , ... go find your own...

## Why did you do this?

My friend Pierre Peigné-Leroy needed to parse movie scripts to make some stylometry on it, as part of his Master Thesis in Philosophy. With a good dataset, a lot can be observed by looking only at the text of our cult movies!

## How can I use it?

> TODO: proper packaging!!

You need python3, and some python packages (I use pip3 to install plugin, but conda or anything else is fine too).

Needed python packages: `argparse, urllib, bs4, re`

Then, you'll need to get my project and launch it. Most of it is interactive, then.

Instructions for Debian/Ubuntu:
```
sudo apt-get install -Y python3 python3-pip git

# Only run "sudo pip..." if you are ready to spend your after-life in Hell.
# Virtualenv would be better.
sudo pip3 install argparse urllib bs4 re

git clone https://github.com/Adrien-Luxey/Da-Fonky-Movie-Script-Parser.git

cd Da-Fonky-Movie-Script-Parser

chmod +x json_querier.py movie_script_parser.py

./movie_script_parser.py
# Follow my interactive lead

./json_querier.py
# Follow instructions again (please)
```

## How does it work?

### Parsing
First of all, we try to find the movie script in the given page. In essence, we look for the first \<pre\> tag, since movie scripts are most often located inside this DOM element.

Once we went through the introduction, we count the number of leading spaces at each line, ask the user for this line's type, and assume that all others lines with this amount of leading spaces must be of the same type. It is a big assumption, but it seems to work nicely most of the time.

Active learning, baby!

### Reading the JSON file

This part is fairly intuitive, just follow the interactive instructions.

## License

Licensed under the [Unlicense](./LICENSE), a.k.a public domain:

> I hereby declare that you can use, modify and sell my script if you feel like it.
>
> In fact, this code is already yours: it's a common good!

-----------------

Contributions welcome etc etc.

Adrien


text:
### Parsing

* Include speech-related stage directions (e.g. "(sneezing)") to the speech blocks

### Modularity

* Parse other types of files (PDF)


text:
I'm coming.

Yeah?

Hi sweetie!

I was talking to the cat.

At least, the cat comes back.

I don't want a million - I just want
one.  A perfect one.

I just found a picture of you.

Like shit.

I don't need one.

Finger!  I'm driving a cab now, not a
space fighter!!

Uh... at least fifty.

Cheers!

Don't watch it all day, it'll rot your
mind. Bye sweetie..

Been here long?

Isn't that a Z140? Alleviated titanium.
Neuro charged assault model?

You know you could hurt someone with
this puppy..good thing it's not loaded..

You gotta push the little yellow button...

You're welcome..

..you know these things are VERY illegal..
you could get in a shit load of trouble..
I better hang onto it for you..

How you doing this morning?  Sleep OK?
I didn't.

I had the worst goddamn nightmare.

Thanks for reminding me..

Right..

You have one point left on your license.
Have a good day.

...'Scuse me?

Sorry, Hon, but I only got one point left
on my license and I gotta get to the garage!

Don't put me in this position...
I can't... I'm late as it is...

Finger's gonna kill me.

I hate when people cry...
I got no defense...

It there's one thing I don't need advice on,
it's how to drive.

We'll wait till things quiet down a bit.
You mind?

You're not that bad... Come on we'll
get you to a doctor.

Vito Cornelius?

Excuse me, I'm looking for a priest.

She's not my bride, she's my fare.
She's looking for this Vito Cornelius.
According to the phone guide he lives here.

She dropped in on me... holding this.

Finger's gonna kill me...

I brought the girl remember?

Yeah! She dropped in on me. I mean
on my taxi... talking... this... this bizarre
language...

You noticed...

So you do know her.

Perfect...

I'm sorry, it's just that... I was
told to wake you up gently, so I figured...

You're right, I was wrong! I shouldn't
have kissed you... especially since we
haven't been introduced and...

Here, it's a bit late, but... my name is
Korben, Korben Dallas.
Keep it, you never know, maybe... you'll
need a cab one day.  I'll be happy to open
the door this time!.

...What's your name?

Hey,  that's... cute... Do you have a nickname,
something a little... shorter?

That's... really cute...

They all like this in your family, father?

Dallas. Korben Dallas.

I might call to check up on her,
you know... to see if she's better?

I know. I was there when she arrived.

Excuse me! Just one thing!  She said something
to me a while ago and... I don't really get it...
Akta Gamat?

That's what I thought.

...Thanks.

Evening...

...Thanks... You, too.

Oh god. I forgot your food ... I'm really sorry!
How about a nice Thai nosh to apologize?
How does that sound, huh?

Hello?

Finger..man..I'm sorry..listen..I was on the way
over but I had a fare fall into my lap..
y'know one of those big fares you just
can't resist..

5'7", green eyes... long legs... great skin...
perfect..

Leeloo..

So you forgive me?

I've never gotten a message that wasn't
bad news.

The last two messages I got?  The first one
was from my wife telling me she was leaving!
And the second was from my lawyer telling me
he was leaving too... with my wife.

At least I won lunch.

Saved by the bell.

...Hello?

Hi Ma...

Yes ma..

Go on... This is gonna take a while!

Other than that... You all right?

I'm coming!. Ma, what're you talking
about?

All I want is an explanation! I just got
in, I lost my job. I smashed my cab.
I got mugged, but other than that
everything's peachy, Ma, thanks for asking!!
Now settle down and explain to me calmly..

Ma. If I'd won, I'd know about it.
Someone would have notified me.

Ma.. it's the door. I'll call you back.

What mission?

Where have I heard this song before?

Just one... why me?

And the third one?

I've had enough good news for today

You rigged the contest?

You couldn't come up with something
a little more discreet?

I'm not going.

One reason... I want to stay the only
one left alive.

...Scuse me.

...Shit!

It's my wife.

I mean my future.. my ex.. My future ex..
if she sees you here I'm finished.  She hates
you guys. It's what killed us in the first place.
Please...

...Sorry, General, but we've got no choice!
It'll only take a minute! Let me set up
another meeting and I'll be back.

Oh, yes you will...

...Coming!

I suppose that means "Hi" ?

Is that the usual way priests go on
vacation?

What kind of mission?

Good luck..

Father, I was in the Army for awhile
and every time they told us we were
on a mission to save the world the
only thing that changed was I lost a
lot of friends. So thanks for the offer..
but no thanks.

I'm sorry..

Leeloo, hide in here and don't move!

Trying to save your ass so you can
save the world.

No, I'm a meat popsicle.

Wrong answer.

I'm really sorry.. there wasn't time.

Here let me wrap you up.

..It's funny.  I've met you twice today  and
you've ended up in my arms both times.

Uh..you're welcome.

Coffee!  That's what you need!
A nice, hot cup of coffee!

With some honey!  You'll see, honey's
great!...

A hot cup of coffee... with honey...

I've got this great honey somewhere.
You know about honey?  There used to be
these little animals who made it with antenna...

..and these other animals who ate it..
one were bees the other were bears..

I forget which ate it and which made it
but..

Here it is!

Taste this...

It... melts in your mouth, doesn't it?

You hear that?

Oh god!

I'm really sorry.. let me help you..

What?

Jesus!... Some priest!

Yeah?

...Not yet.

I'll take the mission.

Hey! I really thought I was going to
miss my flight!

Thanks, kid! You put the luggage on
the conveyor belt?

Great! Now beat it!

Excuse me.  I was so afraid I'd miss the
flight that I sent the kid here to pick up
my boarding card.

...My cousin David...

Uh, yes... Newlyweds.

You know how it is... Love at first sight.
You meet, something goes tilt,. you get
married, you hardly know each other.
Right, darling?

Took the words right out of my mouth.
Go on... I'll be right with you.

It's our honeymoon. We're going to use
the trip to get to know each other better.

Listen... I don't want to be interviewed.
I'd prefer to remain anonymous.

You gotta be kidding!

...Hi.

Uh... not really.

I didn't come here to play Dumbo on the radio.
So tomorrow between 5 and 7 give yourself a
hand, that clear pal?

Not hard to find you...just follow the
Chaos...

Leeloo, listen to me... these tickets...
they're not mine... I mean they are, but
not for vacation like everyone thinks...
I'm on an operation... and if I didn't come
get you, you'd be in a shitload of trouble...
I'd love to be on vacation with you...
but now.... now I've got to work...
And Leeloo... I would love to work in peace.

Yes! But "love" isn't the operative word
here, PEACE is!

Sometimes you can't learn everything
from a screen..sometimes it's better to
ask someone who has experience..

Know what? On that subject maybe you'd
be better off asking the screen.

Finished what?

Which one?

You learned 900 languages in five
minutes?!

I know how to say "Hello". Teach me how
to say "Good-bye", that's all I need.

Apipoussan?

Uh...

Help...

Pardon me.  Excuse me. I'm trying to
reach my wife.. Sorry!

I'll take you to the bar, after that,
you're on your own.

I don't think this is a good time...

Yeah, very big, a giant.

For the concert it says formal attire...
I didn't bring...

Hello?

...Ma???

...Ma!!!

Listen, Ma! I've only got a few days
vacation and I'll be damned if I'm
gonna spend them on the phone.

...Hang on, it's the door. No!
I told you I didn't bring anybody!
I'm all alone... as usual!

...I'll call you back.

Here we go again...

You get that off the screen?

You noticed..

Where you going?

No, not at all!  I mean, just the opposite,
you're... you're beautiful!

I have an idea.

I told you I need to work in peace.
Remember? I need to concentrate.

It's difficult.

Army issue. I'm sorry.

The words you're looking for weren't
in the dictionary you studied. I won't
be long.

No..to all of the above.

I was sent by the government to help you.

I've never heard anything so beautiful
in my life.

Who?

Leeloo... is... she's...

...So Cornelius was telling the truth!

...Yeah, so am I.

I'll help her, I promise, but I think you
should tell me where the Stones are!

I... I don't know! We hardly know
each other... it takes time!

Listen, the last time I admitted to a woman
I loved her ... I never saw her again.

You tell me to save the world then
you go off and leave me in the shit!

Come on! You're not gonna die in peace!
You're not going to die at all! You hear me?
Where are the Stones?

I'm sorry, but... the Stones...

...The Stones are with me?

...In me?

That's it... I've had... all day people
have been sticking guns in my face...

You don't do what I say... I'll waste
you myself. Got it?

Everybody down!

Toss me the balls!

The balls for Christ's sake!

How far is he from here?

Forget it.

Leeloo???

The winner of the Gemini Croquette contest.

Seven to the left. Five to the right.

Six to the left. One to the right.

We got to find the leader. Mangalores
don't fight without a leader.

Found him...

Mind if I go? I'm an excellent
negotiator.

Anyone else want to negotiate?

Yeah, I know... to save the world... but
right now all I want to do is save Leeloo.

When is she not in trouble?

Leeloo?

A molecular bomb.

Not without Leeloo.

It's the latest model.. I've never seen one
before... it works off a magnetic coded
card... Let's see if I can rig it up.

Don't worry, I'm here now!

Just relax. I've got the Stones.
Everything's going to be fine.

I'll be right back.

Don't you know how to count.
It's not all that hard. Watch!

One! That's for trying to kill me!

Two! That's for firing me! Three!
That's for pushing around a priest!
And the rest is for what you did to
my wife!

We're outta here!

It's like a cab isn't it?

Anyone know how to release the lines
on this crate?

Found it?

Fuck it! Hold tight!

Solid little jobs, aren't they?

Apipoulai..

Why? We did pretty well, wouldn't
you say?

I told you not to read all that crap!

So now what's the problem?

How much time before the collision?

I'll call you back in two hours.

Excuse me, Father, but... could we pray
later?

Don't tell me you don't know how all
this works?

Match the symbols!

No. It's much simpler. If we don't figure
out where these Stones go in five minutes,
we're all dead! Think you got it?

There's no light! You told me there were
supposed to be four beams of light.

And you don't know how they open,
is that what you are saying?

Leeloo? The Stones! We have to open
them! How does it work?

I know all that, Leeloo! I'm talking
about the Stones.

The rain falls... the wind blows?

Try and figure out how this fucking thing
opens, instead of staring at me like that!

What did you say? What did you do?

Look, you did something that set it off.
Try to remember. Concentrate. Tell me
exactly what you did!!

Is that all?

The wind! The wind blows...

Quickly, everyone on a Stone!
Water for water!  Fire for fire!
Earth for earth!

Don't breathe.

It's up to you now, Angel!

You can sleep tomorrow... come on...

Leeloo! Listen to me! I'll take you on
a vacation afterwards! A real vacation,
this time, for as long as you want.
Come on! You can do it!

Leeloo! If you don't get on with the
program we're all gonna die! And that's
not on my agenda for today.

You're right but there are lots of good
things... beautiful things...

Exactly.

Yes, you do! I need you. More than you
can imagine! Stand up straight!

Because...

Because...

I love you...


text:
unit PE.Types.Sections;

interface

{$I 'PE.Types.Sections.inc'}

type
  TImageSectionHeader = packed record
  private
    FName: packed array [0 .. IMAGE_SIZEOF_SHORT_NAME - 1] of AnsiChar;
    FVirtualSize: uint32;
    FRVA: uint32;
    FSizeOfRawData: uint32;
    FPointerToRawData: uint32;
    FPointerToRelocations: uint32;
    FPointerToLinenumbers: uint32;
    FNumberOfRelocations: uint16;
    FNumberOfLinenumbers: uint16;
    FFlags: uint32;
  private
    function GetName: string;
    procedure SetName(const Value: string); // length trimmed to 8 chars
  public
    procedure Clear; inline;

    property Name: string read GetName write SetName;
    property VirtualSize: uint32 read FVirtualSize write FVirtualSize;
    property RVA: uint32 read FRVA write FRVA;
    property SizeOfRawData: uint32 read FSizeOfRawData write FSizeOfRawData;
    property PointerToRawData: uint32 read FPointerToRawData write FPointerToRawData;
    property Flags: uint32 read FFlags write FFlags;
  end;

  PImageSectionHeader = ^TImageSectionHeader;

implementation

{ TImageSectionHeader }

procedure TImageSectionHeader.Clear;
begin
  fillchar(self, sizeof(self), 0);
end;

function TImageSectionHeader.GetName: string;
var
  i: Integer;
begin
  i := 0;
  while (i < IMAGE_SIZEOF_SHORT_NAME) and (FName[i] <> #0) do
    inc(i);

  if i = 0 then
    exit('');

  setlength(result, i);

  dec(i);

  while i >= 0 do
  begin
    result[low(result) + i] := char(FName[i]);
    dec(i);
  end;
end;

procedure TImageSectionHeader.SetName(const Value: string);
var
  i, len: Integer;
begin
  i := 0;
  len := length(Value);

  while i < len do
  begin
    FName[i] := AnsiChar(Value[low(Value) + i]);
    inc(i);
  end;

  while i < IMAGE_SIZEOF_SHORT_NAME do
  begin
    FName[i] := #0;
    inc(i);
  end;
end;

end.

repo_name: vdisasm/pe-image-for-delphi

repo_language: Pascal


text:
unit PE.Types;

interface

uses
  System.Generics.Collections,
  PE.Common;

type
  TRVAs = TList<TRVA>;

  TPEParser = class
    FPE: TObject;
    constructor Create(PEImage: TObject);
    function Parse: TParserResult; virtual; abstract;
  end;

  TPEParserClass = class of TPEParser;

implementation

{ TPEParser }

constructor TPEParser.Create(PEImage: TObject);
begin
  FPE := PEImage;
end;

end.


text:
unit PE.Utils;

interface

// When writing padding use PADDINGX string instead of zeros.
{$DEFINE WRITE_PADDING_STRING}

uses
  System.Classes,
  System.SysUtils,

  PE.Common;

function StreamRead(AStream: TStream; var Buf; Count: longint): boolean; inline;
function StreamPeek(AStream: TStream; var Buf; Count: longint): boolean; inline;
function StreamWrite(AStream: TStream; const Buf; Count: longint): boolean; inline;

// Read 0-terminated 1-byte string.
function StreamReadStringA(AStream: TStream; var S: string): boolean;

// Read 0-terminated 2-byte string
function StreamReadStringW(AStream: TStream; var S: string): boolean;

// Write string and return number of bytes written.
// If AlignAfter isn't 0 zero bytes will be written to align it up to AlignAfter value.
function StreamWriteString(AStream: TStream; const S: string; Encoding: TEncoding; AlignAfter: integer = 0): uint32;
// Write ANSI string and return number of bytes written.
function StreamWriteStringA(AStream: TStream; const S: string; AlignAfter: integer = 0): uint32;

const
  PATTERN_PADDINGX: array [0 .. 7] of AnsiChar = ('P', 'A', 'D', 'D', 'I', 'N', 'G', 'X');

  // Write pattern to stream. If Pattern is nil or size of patter is 0 then
  // nulls are written (default).
procedure WritePattern(AStream: TStream; Count: uint32; Pattern: Pointer = nil; PatternSize: integer = 0);

function StreamSeek(AStream: TStream; Offset: TFileOffset): boolean; inline;
function StreamSkip(AStream: TStream; Count: integer = 1): boolean; inline;

// Seek from current position to keep alignment.
function StreamSeekAlign(AStream: TStream; Align: integer): boolean; inline;

// Try to seek Offset and insert padding if Offset < stream Size.
procedure StreamSeekWithPadding(AStream: TStream; Offset: TFileOffset);

function Min(const A, B: uint64): uint64; inline; overload;
function Min(const A, B, C: uint64): uint64; inline; overload;

function Max(const A, B: uint64): uint64; inline;

function AlignUp(Value: uint64; Align: uint32): uint64; inline;
function AlignDown(Value: uint64; Align: uint32): uint64; inline;

function IsAlphaNumericString(const S: String): boolean;

function CompareRVA(A, B: TRVA): integer; inline;

function ReplaceSpecialSymobls(const source: string): string;

implementation

{ Stream }

function StreamRead(AStream: TStream; var Buf; Count: longint): boolean;
begin
  Result := AStream.Read(Buf, Count) = Count;
end;

function StreamPeek(AStream: TStream; var Buf; Count: longint): boolean; inline;
var
  Read: integer;
begin
  Read := AStream.Read(Buf, Count);
  AStream.Seek(-Read, soFromCurrent);
  Result := Read = Count;
end;

function StreamWrite(AStream: TStream; const Buf; Count: longint): boolean;
begin
  Result := AStream.Write(Buf, Count) = Count;
end;

function StreamReadStringA(AStream: TStream; var S: string): boolean;
var
  C: byte;
begin
  S := '';
  while True do
    if AStream.Read(C, SizeOf(C)) <> SizeOf(C) then
      break
    else if (C = 0) then
      exit(True)
    else
      S := S + Char(C);
  exit(False);
end;

function StreamReadStringW(AStream: TStream; var S: string): boolean;
var
  C: word;
begin
  S := '';
  while True do
    if AStream.Read(C, SizeOf(C)) <> SizeOf(C) then
      break
    else if (C = 0) then
      exit(True)
    else
      S := S + Char(C);
  exit(False);
end;

function StreamWriteString(AStream: TStream; const S: string; Encoding: TEncoding; AlignAfter: integer): uint32;
var
  Bytes: TBytes;
begin
  Bytes := Encoding.GetBytes(S);
  Result := AStream.Write(Bytes, Length(Bytes));

  if AlignAfter <> 0 then
  begin
    // Number of bytes left to write to be aligned.
    AlignAfter := AlignAfter - (AStream.Size mod AlignAfter);
    WritePattern(AStream, AlignAfter, nil, 0);
  end;
end;

function StreamWriteStringA(AStream: TStream; const S: string; AlignAfter: integer): uint32;
begin
  Result := StreamWriteString(AStream, S, TEncoding.ANSI, AlignAfter);
end;

procedure WritePattern(AStream: TStream; Count: uint32; Pattern: Pointer; PatternSize: integer);
var
  p: pbyte;
  i: integer;
begin
  if Count = 0 then
    exit;

  if Assigned(Pattern) and (PatternSize > 0) then
  begin
    p := GetMemory(Count);
    if PatternSize = 1 then
      FillChar(p^, Count, pbyte(Pattern)^)
    else
    begin
      for i := 0 to Count - 1 do
        p[i] := pbyte(Pattern)[i mod PatternSize];
    end;
  end
  else
  begin
    p := AllocMem(Count); // filled with nulls
  end;

  try
    AStream.Write(p^, Count);
  finally
    FreeMem(p);
  end;
end;

function StreamSeek(AStream: TStream; Offset: TFileOffset): boolean;
begin
  Result := AStream.Seek(Offset, TSeekOrigin.soBeginning) = Offset;
end;

function StreamSkip(AStream: TStream; Count: integer): boolean; inline;
var
  Offset: TFileOffset;
begin
  Offset := AStream.Position + Count;
  Result := AStream.Seek(Offset, TSeekOrigin.soBeginning) = Offset;
end;

function StreamSeekAlign(AStream: TStream; Align: integer): boolean;
var
  m: integer;
  pos: TFileOffset;
begin
  if Align in [0, 1] then
    exit(True); // don't need alignment
  pos := AStream.Position;
  m := pos mod Align;
  if m = 0 then
    exit(True);        // already aligned
  inc(pos, Align - m); // next aligned position
  Result := AStream.Seek(pos, TSeekOrigin.soBeginning) = pos;
end;

procedure StreamSeekWithPadding(AStream: TStream; Offset: TFileOffset);
begin
  if Offset <= AStream.Size then
  begin
    AStream.Seek(Offset, TSeekOrigin.soBeginning);
    exit;
  end;
  // Insert padding if need.
  AStream.Seek(AStream.Size, TSeekOrigin.soBeginning);
  WritePattern(AStream, Offset - AStream.Size, nil, 0);
end;

{ Min / Max }

function Min(const A, B: uint64): uint64;
begin
  if A < B then
    exit(A)
  else
    exit(B);
end;

function Min(const A, B, C: uint64): uint64;
begin
  Result := Min(Min(A, B), C);
end;

function Max(const A, B: uint64): uint64;
begin
  if A > B then
    exit(A)
  else
    exit(B);
end;

{ AlignUp }

function AlignUp(Value: uint64; Align: uint32): uint64;
var
  d, m: uint32;
begin
  d := Value div Align;
  m := Value mod Align;
  if m = 0 then
    Result := Value
  else
    Result := (d + 1) * Align;
end;

function AlignDown(Value: uint64; Align: uint32): uint64;
begin
  Result := (Value div Align) * Align;
end;

function IsAlphaNumericString(const S: String): boolean;
const
  ALLOWED_CHARS = ['0' .. '9', 'A' .. 'Z', 'a' .. 'z'];
var
  C: Char;
begin
  for C in S do
    if not CharInSet(c, ALLOWED_CHARS) then
      exit(False);
  exit(True);
end;

function CompareRVA(A, B: TRVA): integer;
begin
  if A > B then
    exit(1)
  else if A < B then
    exit(-1)
  else
    exit(0);
end;

function ReplaceSpecialSymobls(const source: string): string;
begin
  Result := source.
    Replace(#10, '\n').
    Replace(#13, '\r');
end;

end.


text:
unit PE.Types.TLS;

interface

uses
  PE.Common;

// 5.7.1. The TLS Directory
type
  TTLSDirectory32 = packed record
    RawDataStartVA: uint32;     // The starting address of the TLS template. The template is a block of data that is used to initialize TLS data. The system copies all of this data each time a thread is created, so it must not be corrupted. Note that this address is not an RVA; it is an address for which there should be a base relocation in the .reloc section.
    RawDataEndVA: uint32;       // The address of the last byte of the TLS, except for the zero fill. As with the Raw Data Start VA field, this is a VA, not an RVA.
    AddressofIndex: uint32;     // The location to receive the TLS index, which the loader assigns. This location is in an ordinary data section, so it can be given a symbolic name that is accessible to the program.
    AddressofCallbacks: uint32; // The pointer to an array of TLS callback functions. The array is null-terminated, so if no callback function is supported, this field points to 4 bytes set to zero. For information about the prototype for these functions, see section 6.7.2, “TLS Callback Functions.”
    SizeofZeroFill: uint32;     // The size in bytes of the template, beyond the initialized data delimited by the Raw Data Start VA and Raw Data End VA fields. The total template size should be the same as the total size of TLS data in the image file. The zero fill is the amount of data that comes after the initialized nonzero data.
    Characteristics: uint32;    // Reserved for possible future use by TLS flags.
  end;

  TTLSDirectory64 = packed record
    RawDataStartVA: uint64;     // The starting address of the TLS template. The template is a block of data that is used to initialize TLS data. The system copies all of this data each time a thread is created, so it must not be corrupted. Note that this address is not an RVA; it is an address for which there should be a base relocation in the .reloc section.
    RawDataEndVA: uint64;       // The address of the last byte of the TLS, except for the zero fill. As with the Raw Data Start VA field, this is a VA, not an RVA.
    AddressofIndex: uint64;     // The location to receive the TLS index, which the loader assigns. This location is in an ordinary data section, so it can be given a symbolic name that is accessible to the program.
    AddressofCallbacks: uint64; // The pointer to an array of TLS callback functions. The array is null-terminated, so if no callback function is supported, this field points to 4 bytes set to zero. For information about the prototype for these functions, see section 6.7.2, “TLS Callback Functions.”
    SizeofZeroFill: uint32;     // The size in bytes of the template, beyond the initialized data delimited by the Raw Data Start VA and Raw Data End VA fields. The total template size should be the same as the total size of TLS data in the image file. The zero fill is the amount of data that comes after the initialized nonzero data.
    Characteristics: uint32;    // Reserved for possible future use by TLS flags.
  end;

  TTLSDirectory = packed record
    case integer of
      32: (tls32: TTLSDirectory32);
      64: (tls64: TTLSDirectory64);
    end;

  PIMAGE_TLS_CALLBACK = procedure(DllHandle: pointer; Reason: dword; Reserved: pointer); stdcall;

implementation

end.


text:
{
  Unified Optional Header.
  Represents both 32 and 64 bit.
  Directories not included.
}

unit PE.Headers;

interface

uses
  System.Classes;

type

  { **********************************************************************************************************************
    * Comparision of optional headers
    **********************************************************************************************************************
    *  TImageOptionalHeader32 = packed record                    TImageOptionalHeader64 = packed record
    *
    *    // Standard fields.                                       // Standard fields.
    *    Magic                    : uint16;                        Magic                    : uint16;
    *
    *    MajorLinkerVersion       : uint8;                         MajorLinkerVersion       : uint8;
    *    MinorLinkerVersion       : uint8;                         MinorLinkerVersion       : uint8;
    *    SizeOfCode               : uint32;                        SizeOfCode               : uint32;
    *    SizeOfInitializedData    : uint32;                        SizeOfInitializedData    : uint32;
    *    SizeOfUninitializedData  : uint32;                        SizeOfUninitializedData  : uint32;
    *    AddressOfEntryPoint      : uint32;                        AddressOfEntryPoint      : uint32;
    *    BaseOfCode               : uint32;                        BaseOfCode               : uint32;
    *
    *    BaseOfData               : uint32;   // PE32 only
    *
    *    // NT additional fields.                                  // NT additional fields.
    *    ImageBase                    : uint32;                    ImageBase                    : uint64;
    *
    *    SectionAlignment             : uint32;                    SectionAlignment             : uint32;
    *    FileAlignment                : uint32;                    FileAlignment                : uint32;
    *    MajorOperatingSystemVersion  : uint16;                    MajorOperatingSystemVersion  : uint16;
    *    MinorOperatingSystemVersion  : uint16;                    MinorOperatingSystemVersion  : uint16;
    *    MajorImageVersion            : uint16;                    MajorImageVersion            : uint16;
    *    MinorImageVersion            : uint16;                    MinorImageVersion            : uint16;
    *    MajorSubsystemVersion        : uint16;                    MajorSubsystemVersion        : uint16;
    *    MinorSubsystemVersion        : uint16;                    MinorSubsystemVersion        : uint16;
    *    Win32VersionValue            : uint32;                    Win32VersionValue            : uint32;
    *    SizeOfImage                  : uint32;                    SizeOfImage                  : uint32;
    *    SizeOfHeaders                : uint32;                    SizeOfHeaders                : uint32;
    *    CheckSum                     : uint32;                    CheckSum                     : uint32;
    *    Subsystem                    : uint16;                    Subsystem                    : uint16;
    *    DllCharacteristics           : uint16;                    DllCharacteristics           : uint16;
    *
    *    SizeOfStackReserve           : uint32;                    SizeOfStackReserve           : uint64;
    *    SizeOfStackCommit            : uint32;                    SizeOfStackCommit            : uint64;
    *    SizeOfHeapReserve            : uint32;                    SizeOfHeapReserve            : uint64;
    *    SizeOfHeapCommit             : uint32;                    SizeOfHeapCommit             : uint64;
    *
    *    LoaderFlags                  : uint32;                    LoaderFlags                  : uint32;
    *    NumberOfRvaAndSizes          : uint32;                    NumberOfRvaAndSizes          : uint32;
    *
    *    DataDirectories              : TImageDataDirectories;     DataDirectories              : TImageDataDirectories;
    * end;                                                      end;
    **********************************************************************************************************************
  }

  UIntCommon = uint64;

  TPEOptionalHeader = packed record

    // Standard fields.
    Magic: uint16;

    MajorLinkerVersion: uint8;
    MinorLinkerVersion: uint8;
    SizeOfCode: uint32;
    SizeOfInitializedData: uint32;
    SizeOfUninitializedData: uint32;
    AddressOfEntryPoint: uint32;
    BaseOfCode: uint32;

    // PE32 only
    BaseOfData: uint32;

    // NT additional fields.
    ImageBase: UIntCommon;

    SectionAlignment: uint32;
    FileAlignment: uint32;
    MajorOperatingSystemVersion: uint16;
    MinorOperatingSystemVersion: uint16;
    MajorImageVersion: uint16;
    MinorImageVersion: uint16;
    MajorSubsystemVersion: uint16;
    MinorSubsystemVersion: uint16;
    Win32VersionValue: uint32;
    SizeOfImage: uint32;
    SizeOfHeaders: uint32;
    CheckSum: uint32;
    Subsystem: uint16;
    DllCharacteristics: uint16;

    SizeOfStackReserve: UIntCommon;
    SizeOfStackCommit: UIntCommon;
    SizeOfHeapReserve: UIntCommon;
    SizeOfHeapCommit: UIntCommon;

    LoaderFlags: uint32;
    NumberOfRvaAndSizes: uint32;

    // Return number of bytes read.
    function ReadFromStream(Stream: TStream; ImageBits: uint32; MaxSize: integer): uint32;

    // Return number of bytes written.
    function WriteToStream(Stream: TStream; ImageBits: uint32; MaxSize: integer): uint32;

    // Calcualte size of normal optional header.
    function CalcSize(ImageBits: uint32): uint32;

  end;

  PPEOptionalHeader = ^TPEOptionalHeader;

implementation

uses
  System.SysUtils,
  PE.RTTI;

const
  RF_SIZE8       = 1 shl 0;
  RF_SIZE16      = 1 shl 1;
  RF_SIZE32      = 1 shl 2;
  RF_SIZE64      = 1 shl 3;
  RF_SIZEMACHINE = 1 shl 4; // Size is (32 or 64) in 64 bit slot.
  RF_PE32        = 1 shl 5; // Present in 32-bit image.
  RF_PE64        = 1 shl 6; // Present in 64-bit image.

  RF_PE3264 = RF_PE32 or RF_PE64;

  COMMON_08      = RF_SIZE8 or RF_PE3264;
  COMMON_16      = RF_SIZE16 or RF_PE3264;
  COMMON_32      = RF_SIZE32 or RF_PE3264;
  COMMON_MACHINE = RF_SIZEMACHINE or RF_PE3264;

const
  MACHINE_DWORD_SIZE = -1;

  OptionalHeaderFieldDesc: packed array [0 .. 29] of TRecordFieldDesc =
    (
    (Flags: COMMON_16; FieldName: 'Magic'),

    (Flags: COMMON_08; FieldName: 'MajorLinkerVersion'),
    (Flags: COMMON_08; FieldName: 'MinorLinkerVersion'),
    (Flags: COMMON_32; FieldName: 'SizeOfCode'),
    (Flags: COMMON_32; FieldName: 'SizeOfInitializedData'),
    (Flags: COMMON_32; FieldName: 'SizeOfUninitializedData'),
    (Flags: COMMON_32; FieldName: 'AddressOfEntryPoint'),
    (Flags: COMMON_32; FieldName: 'BaseOfCode'),

    (Flags: RF_SIZE32 or RF_PE32; FieldName: 'BaseOfData'),

    (Flags: COMMON_MACHINE; FieldName: 'ImageBase'),

    (Flags: COMMON_32; FieldName: 'SectionAlignment'),
    (Flags: COMMON_32; FieldName: 'FileAlignment'),
    (Flags: COMMON_16; FieldName: 'MajorOperatingSystemVersion'),
    (Flags: COMMON_16; FieldName: 'MinorOperatingSystemVersion'),
    (Flags: COMMON_16; FieldName: 'MajorImageVersion'),
    (Flags: COMMON_16; FieldName: 'MinorImageVersion'),
    (Flags: COMMON_16; FieldName: 'MajorSubsystemVersion'),
    (Flags: COMMON_16; FieldName: 'MinorSubsystemVersion'),
    (Flags: COMMON_32; FieldName: 'Win32VersionValue'),
    (Flags: COMMON_32; FieldName: 'SizeOfImage'),
    (Flags: COMMON_32; FieldName: 'SizeOfHeaders'),
    (Flags: COMMON_32; FieldName: 'CheckSum'),
    (Flags: COMMON_16; FieldName: 'Subsystem'),
    (Flags: COMMON_16; FieldName: 'DllCharacteristics'),

    (Flags: COMMON_MACHINE; FieldName: 'SizeOfStackReserve'),
    (Flags: COMMON_MACHINE; FieldName: 'SizeOfStackCommit'),
    (Flags: COMMON_MACHINE; FieldName: 'SizeOfHeapReserve'),
    (Flags: COMMON_MACHINE; FieldName: 'SizeOfHeapCommit'),

    (Flags: COMMON_32; FieldName: 'LoaderFlags'),
    (Flags: COMMON_32; FieldName: 'NumberOfRvaAndSizes')
    );

type
  TPECtx = record
    ImageBits: uint32;
  end;

  PPECtx = ^TPECtx;

procedure ResolveProc(Desc: PRecordFieldDesc; OutFieldSize, OutEffectiveSize: PInteger; ud: pointer);
begin
  OutFieldSize^ := 0;
  OutEffectiveSize^ := 0;

  // OutFieldSize (mandatory)
  if ((Desc^.Flags and RF_SIZE8) <> 0) then
    OutFieldSize^ := 1
  else if ((Desc^.Flags and RF_SIZE16) <> 0) then
    OutFieldSize^ := 2
  else if ((Desc^.Flags and RF_SIZE32) <> 0) then
    OutFieldSize^ := 4
  else if ((Desc^.Flags and RF_SIZE64) <> 0) then
    OutFieldSize^ := 8
  else if ((Desc^.Flags and RF_SIZEMACHINE) <> 0) then
    OutFieldSize^ := SizeOf(UIntCommon)
  else
    raise Exception.Create('Unsupported image.');

  if ((Desc^.Flags and RF_PE3264) = RF_PE32) and (PPECtx(ud)^.ImageBits <> 32) then
    exit;
  if ((Desc^.Flags and RF_PE3264) = RF_PE64) and (PPECtx(ud)^.ImageBits <> 64) then
    exit;

  // OutEffectiveSize
  if ((Desc^.Flags and RF_SIZE8) <> 0) then
    OutEffectiveSize^ := 1
  else if ((Desc^.Flags and RF_SIZE16) <> 0) then
    OutEffectiveSize^ := 2
  else if ((Desc^.Flags and RF_SIZE32) <> 0) then
    OutEffectiveSize^ := 4
  else if ((Desc^.Flags and RF_SIZE64) <> 0) then
    OutEffectiveSize^ := 8
  else if ((Desc^.Flags and RF_SIZEMACHINE) <> 0) then
    OutEffectiveSize^ := PPECtx(ud)^.ImageBits div 8
  else
    raise Exception.Create('Unsupported image.');

end;

{ TPEOptionalHeader }

function TPEOptionalHeader.CalcSize(ImageBits: uint32): uint32;
var
  ctx: TPECtx;
begin
  ctx.ImageBits := ImageBits;

  Result := RTTI_Process(nil, RttiCalcSize, @Self,
    @OptionalHeaderFieldDesc[0], Length(OptionalHeaderFieldDesc),
    -1,
    ResolveProc,
    @ctx);
end;

function TPEOptionalHeader.ReadFromStream;
var
  ctx: TPECtx;
begin
  // Not all fields can be read, so must clear whole structure.
  FillChar(Self, SizeOf(Self), 0);

  ctx.ImageBits := ImageBits;

  Result := RTTI_Process(Stream, RttiRead, @Self,
    @OptionalHeaderFieldDesc[0], Length(OptionalHeaderFieldDesc),
    MaxSize,
    ResolveProc,
    @ctx);
end;

function TPEOptionalHeader.WriteToStream;
var
  ctx: TPECtx;
begin
  ctx.ImageBits := ImageBits;

  Result := RTTI_Process(Stream, RttiWrite, @Self,
    @OptionalHeaderFieldDesc[0], Length(OptionalHeaderFieldDesc),
    MaxSize,
    ResolveProc,
    @ctx);
end;

end.


text:
{
  Memory Stream based on already mapped PE image in current process.
  Basically it's TMemoryStream with Memory pointing to ImageBase and Size equal
  to SizeOfImage.
}
unit PE.MemoryStream;

interface

uses
  System.Classes,
  System.SysUtils;

type
  TPECustomMemoryStream = class(TStream)
  protected
    FMemory: Pointer;
    FSize, FPosition: NativeInt;
  public
    constructor CreateFromPointer(Ptr: Pointer; Size: integer);

    procedure SetPointer(Ptr: Pointer; const Size: NativeInt);

    function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;

    function Read(var Buffer; Count: Longint): Longint; override;
    function Write(const Buffer; Count: integer): integer; override;

    procedure SaveToStream(Stream: TStream); virtual;
    procedure SaveToFile(const FileName: string);

    property Memory: Pointer read FMemory;
  end;

  TPEMemoryStream = class(TPECustomMemoryStream)
  private
    FModuleToUnload: HMODULE; // needed if module loading is forced.
    FModuleFileName: string;
    FModuleSize: uint32;
  private
    procedure CreateFromModulePtr(ModulePtr: Pointer);
  public
    // Create stream from module in current process.
    // If module is not found exception raised.
    // To force loading module set ForceLoadingModule to True.
    constructor Create(const ModuleName: string; ForceLoadingModule: boolean = False); overload;

    // Create from module by known base address.
    constructor Create(ModuleBase: NativeUInt); overload;

    destructor Destroy; override;

    // Simply read SizeOfImage from memory.
    class function GetModuleImageSize(ModulePtr: PByte): uint32; static;
  end;

implementation

uses
  WinApi.Windows,

  PE.Types.DosHeader,
  PE.Types.NTHeaders;

{ TPECustomMemoryStream }

procedure TPECustomMemoryStream.SetPointer(Ptr: Pointer; const Size: NativeInt);
begin
  FMemory := Ptr;
  FSize := Size;
  FPosition := 0;
end;

constructor TPECustomMemoryStream.CreateFromPointer(Ptr: Pointer; Size: integer);
begin
  inherited Create;
  SetPointer(Ptr, Size);
end;

procedure TPECustomMemoryStream.SaveToFile(const FileName: string);
var
  Stream: TStream;
begin
  Stream := TFileStream.Create(FileName, fmCreate);
  try
    SaveToStream(Stream);
  finally
    Stream.Free;
  end;
end;

procedure TPECustomMemoryStream.SaveToStream(Stream: TStream);
begin
  if FSize <> 0 then
    Stream.WriteBuffer(FMemory^, FSize);
end;

function TPECustomMemoryStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
begin
  case Origin of
    soBeginning:
      FPosition := Offset;
    soCurrent:
      Inc(FPosition, Offset);
    soEnd:
      FPosition := FSize + Offset;
  end;
  Result := FPosition;
end;

function TPECustomMemoryStream.Read(var Buffer; Count: integer): Longint;
begin
  if Count = 0 then
    Exit(0);
  Result := FSize - FPosition;
  if Result > 0 then
  begin
    if Result > Count then
      Result := Count;
    Move(PByte(FMemory)[FPosition], Buffer, Result);
    Inc(FPosition, Result);
  end;
end;

function TPECustomMemoryStream.Write(const Buffer; Count: integer): integer;
begin
  if Count = 0 then
    Exit(0);
  Result := FSize - FPosition;
  if Result > 0 then
  begin
    if Result > Count then
      Result := Count;
    Move(Buffer, PByte(FMemory)[FPosition], Result);
    Inc(FPosition, Result);
  end;
end;

{ TPEMemoryStream }

procedure TPEMemoryStream.CreateFromModulePtr(ModulePtr: Pointer);
begin
  if ModulePtr = nil then
    raise Exception.CreateFmt('Module "%s" not found in address space',
      [FModuleFileName]);

  FModuleSize := TPEMemoryStream.GetModuleImageSize(ModulePtr);

  SetPointer(ModulePtr, FModuleSize);
end;

constructor TPEMemoryStream.Create(const ModuleName: string;
  ForceLoadingModule: boolean);
var
  FModulePtr: Pointer;
begin
  inherited Create;
  FModuleFileName := ModuleName;

  FModulePtr := Pointer(GetModuleHandle(PChar(ModuleName)));
  FModuleToUnload := 0;

  if (FModulePtr = nil) and (ForceLoadingModule) then
  begin
    FModuleToUnload := LoadLibrary(PChar(ModuleName));
    FModulePtr := Pointer(FModuleToUnload);
  end;

  CreateFromModulePtr(FModulePtr);
end;

constructor TPEMemoryStream.Create(ModuleBase: NativeUInt);
begin
  inherited Create;
  FModuleFileName := GetModuleName(ModuleBase);

  FModuleToUnload := 0; // we didn't load it and won't free it

  CreateFromModulePtr(Pointer(ModuleBase));
end;

destructor TPEMemoryStream.Destroy;
begin
  if FModuleToUnload <> 0 then
    FreeLibrary(FModuleToUnload);
  inherited;
end;

class function TPEMemoryStream.GetModuleImageSize(ModulePtr: PByte): uint32;
var
  dos: PImageDOSHeader;
  nt: PImageNTHeaders;
begin
  dos := PImageDOSHeader(ModulePtr);

  if not dos.e_magic.IsMZ then
    raise Exception.Create('Not PE image');

  nt := PImageNTHeaders(ModulePtr + dos^.e_lfanew);

  if not nt.Signature.IsPE00 then
    raise Exception.Create('Not PE image');

  Result := nt^.OptionalHeader.pe32.SizeOfImage;
end;

end.


text:
{
  PE Exported symbols are case-sensitive.
}
unit PE.ExportSym;

interface

uses
  System.Generics.Collections,
  System.SysUtils,
  PE.Common;

type

  { TPEExportSym }

  TPEExportSym = class
  public
    RVA: TRVA;
    Ordinal: uint32;
    Name: String;
    ForwarderName: String;
    Forwarder: boolean;

    // Is this symbol has RVA or is forwarder.
    function IsValid: boolean; inline;

    procedure Clear;
    function Clone: TPEExportSym;

    // Parse forwarder name of following structure: "LibName.FuncName".
    // Return true if both names before and after dot found.
    function GetForwarderLibAndFuncName(out Lib, Name: string): boolean;
  end;

  PPEExportSym = ^TPEExportSym;

  TPEExportSymVec = TList<TPEExportSym>;
  TPEExportSymByRVA = TDictionary<TRVA, TPEExportSym>;

  { TPEExportSyms }

  TPEExportSyms = class
  private
    FItems: TPEExportSymVec;
    // FItemsByRVA: TPEExportSymByRVA;
    function GetCount: integer;
    procedure ExportSymNotify(Sender: TObject; const Item: TPEExportSym;
      Action: TCollectionNotification);
  public
    constructor Create;
    destructor Destroy; override;

    // Add item to list of symbols.
    // If SetOrdinal is True, src Item ordinal will be set to last sym number.
    procedure Add(Sym: TPEExportSym; SetOrdinal: boolean = false);

    // Add symbol by Name.
    procedure AddByName(RVA: TRVA; const Name: String);

    // Usually you don't need to set Ordinal, because ordinals are auto-incremented.
    procedure AddByOrdinal(RVA: TRVA; Ordinal: dword = 0);

    procedure AddForwarder(const Name, ForwarderName: String);

    procedure Clear;

    // Get item by RVA or nil if not found.
    // todo: there can be many exports with same RVA
    // function GetItemByRVA(RVA: TRVA): TPEExportSym; inline;

    property Count: integer read GetCount;
    property Items: TPEExportSymVec read FItems;
  end;

implementation

function TPEExportSym.Clone: TPEExportSym;
begin
  result := TPEExportSym.Create;
  result.RVA := self.RVA;
  result.Ordinal := self.Ordinal;
  result.Name := self.Name;
  result.ForwarderName := self.ForwarderName;
  result.Forwarder := self.Forwarder;
end;

function TPEExportSym.GetForwarderLibAndFuncName(out Lib, Name: string): boolean;
var
  arr: TArray<string>;
begin
  arr := string(ForwarderName).Split(['.']);
  result := length(arr) = 2;
  if result then
  begin
    Lib := arr[0];
    name := arr[1];
  end
  else
  begin
    Lib := '';
    name := '';
  end;
end;

function TPEExportSym.IsValid: boolean;
begin
  // Either forwarder or has rva.
  result := Forwarder or (RVA <> 0);
end;

procedure TPEExportSym.Clear;
begin
  RVA := 0;
  Ordinal := 0;
  Name := '';
  ForwarderName := '';
  Forwarder := false;
end;

{ TExportSyms }

procedure TPEExportSyms.Add(Sym: TPEExportSym; SetOrdinal: boolean = false);
begin
  if SetOrdinal then
    Sym.Ordinal := FItems.Count + 1;
  FItems.Add(Sym);
end;

procedure TPEExportSyms.AddByName(RVA: TRVA; const Name: String);
var
  Sym: TPEExportSym;
begin
  Sym := TPEExportSym.Create;
  Sym.RVA := RVA;
  Sym.Name := Name;
  Add(Sym, True);
end;

procedure TPEExportSyms.AddByOrdinal(RVA: TRVA; Ordinal: dword);
var
  Sym: TPEExportSym;
begin
  Sym := TPEExportSym.Create;
  Sym.RVA := RVA;
  Sym.Ordinal := Ordinal;
  Add(Sym, Ordinal = 0);
end;

procedure TPEExportSyms.AddForwarder(const Name, ForwarderName: String);
var
  Sym: TPEExportSym;
begin
  Sym := TPEExportSym.Create;
  Sym.Name := Name;
  Sym.ForwarderName := ForwarderName;
  Sym.Forwarder := True;
  Add(Sym, True);
end;

procedure TPEExportSyms.Clear;
begin
  FItems.Clear;
  // FItemsByRVA.Clear;
end;

constructor TPEExportSyms.Create;
begin
  FItems := TPEExportSymVec.Create;
  FItems.OnNotify := ExportSymNotify;

  // FItemsByRVA := TPEExportSymByRVA.Create;
end;

destructor TPEExportSyms.Destroy;
begin
  // FItemsByRVA.Free;
  FItems.Free;
  inherited;
end;

procedure TPEExportSyms.ExportSymNotify(Sender: TObject;
  const Item: TPEExportSym; Action: TCollectionNotification);
begin
  if Action = cnRemoved then
    Item.Free;
end;

function TPEExportSyms.GetCount: integer;
begin
  result := FItems.Count;
end;

// function TPEExportSyms.GetItemByRVA(RVA: TRVA): TPEExportSym;
// begin
// if not FItemsByRVA.TryGetValue(RVA, result) then
// result := nil;
// end;

end.


text:
unit PE.Section;

interface

uses
  System.Classes,
  System.SysUtils,

  PE.Common,
  PE.Msg,
  PE.Types,
  PE.Types.Sections,
  PE.Utils;

type
  TPESectionBase = class
  private
    FMsg: PMsgMgr;
    FName: String;      // Section name.
    FVSize: uint32;     // Virtual Size.
    FRVA: TRVA;         // Relative Virtual Address.
    FRawSize: uint32;   // Raw size.
    FRawOffset: uint32; // Raw offset.
    FFlags: uint32;     // Section flags.
    FMem: TBytes;       // Memory allocated for section, size = raw size
    function GetImageSectionHeader: TImageSectionHeader;
    function GetMemPtr: PByte;

    procedure SetAllocatedSize(Value: uint32);
  public

    constructor Create(const ASecHdr: TImageSectionHeader; AMem: pointer;
      AMsg: PMsgMgr = nil); overload;

    destructor Destroy; override;

    function GetAllocatedSize: uint32; inline;

    // Set section values from Section Header.
    // Allocate memory for section data.
    // If ChangeData is True memory will be overwritten.
    procedure SetHeader(ASecHdr: TImageSectionHeader; ASrcData: pointer;
      ChangeData: boolean = True);

    // Can be used to load mapped section.
    // SetHeader must be called first.
    function LoadDataFromStreamEx(AStream: TStream;
      ARawOffset, ARawSize: uint32): boolean;

    // Allocate Mem and read RawSize bytes from RawOffset of AStream.
    function LoadDataFromStream(AStream: TStream): boolean;

    // Save section data to AStream.
    function SaveDataToStream(AStream: TStream): boolean;

    // Save section data to file.
    function SaveToFile(const FileName: string): boolean;

    // Deallocate section data.
    procedure ClearData;

    procedure Resize(NewSize: uint32);

    function ContainRVA(RVA: TRVA): boolean; {$IFNDEF DEBUG}inline; {$ENDIF}
    function GetEndRVA: TRVA; inline;
    function GetEndRawOffset: uint32; inline;
    function GetLastRVA: TRVA; inline;

    function IsCode: boolean; inline;

    function NameAsHex: string;

    property Name: String read FName write FName;
    property VirtualSize: uint32 read FVSize;
    property RVA: TRVA read FRVA;
    property RawSize: uint32 read FRawSize;
    property RawOffset: uint32 read FRawOffset write FRawOffset;
    property Flags: uint32 read FFlags write FFlags;
    property ImageSectionHeader: TImageSectionHeader read GetImageSectionHeader;
    property AllocatedSize: uint32 read GetAllocatedSize;

    // Don't use Mem directly.
    // Use TPEImage functions to read/write data.
    property Mem: PByte read GetMemPtr;
  end;

  TPESectionImageHeader = class(TPESectionBase);

  TPESection = class(TPESectionBase);

  PPESection = ^TPESection;

implementation

{ TPESection }

function TPESectionBase.ContainRVA(RVA: TRVA): boolean;
begin
  Result := (RVA >= Self.RVA) and (RVA < Self.GetEndRVA);
end;

constructor TPESectionBase.Create(const ASecHdr: TImageSectionHeader; AMem: pointer;
  AMsg: PMsgMgr);
begin
  FMsg := AMsg;
  SetHeader(ASecHdr, AMem);
end;

destructor TPESectionBase.Destroy;
begin
  ClearData;
  inherited;
end;

function TPESectionBase.SaveToFile(const FileName: string): boolean;
var
  fs: TFileStream;
begin
  try
    fs := TFileStream.Create(FileName, fmCreate or fmShareDenyWrite);
    try
      fs.Write(Self.Mem^, Self.FVSize);
      Result := True;
    finally
      FreeAndNil(fs);
    end;
  except
    Result := false;
  end;
end;

procedure TPESectionBase.SetAllocatedSize(Value: uint32);
begin
  SetLength(FMem, Value);
end;

procedure TPESectionBase.SetHeader(ASecHdr: TImageSectionHeader; ASrcData: pointer;
  ChangeData: boolean);
var
  SizeToAlloc: uint32;
begin
  FName := ASecHdr.Name;
  FVSize := ASecHdr.VirtualSize;
  FRVA := ASecHdr.RVA;
  FRawSize := ASecHdr.SizeOfRawData;
  FRawOffset := ASecHdr.PointerToRawData;
  FFlags := ASecHdr.Flags;

  if ChangeData then
  begin
    SizeToAlloc := FVSize;

    if SizeToAlloc = 0 then
      raise Exception.Create('Section data size = 0.');

    // If no source mem specified, alloc empty block.
    // If have source mem, copy it.
    if ASrcData = nil then
    begin
      SetAllocatedSize(0);
      SetAllocatedSize(SizeToAlloc);
    end
    else
    begin
      SetAllocatedSize(SizeToAlloc);
      Move(ASrcData^, Mem^, SizeToAlloc);
    end;
  end;
end;

procedure TPESectionBase.ClearData;
begin
  SetAllocatedSize(0);
  FRawSize := 0;
  FRawOffset := 0;
end;

function TPESectionBase.GetEndRVA: TRVA;
begin
  Result := Self.RVA + Self.VirtualSize;
end;

function TPESectionBase.GetLastRVA: TRVA;
begin
  Result := Self.RVA + Self.VirtualSize - 1;
end;

function TPESectionBase.GetImageSectionHeader: TImageSectionHeader;
begin
  Result.Clear;
  Result.Name := FName;
  Result.RVA := RVA;
  Result.VirtualSize := VirtualSize;
  Result.SizeOfRawData := RawSize;
  Result.PointerToRawData := RawOffset;
  Result.Flags := Flags;
end;

function TPESectionBase.GetMemPtr: PByte;
begin
  Result := @FMem[0];
end;

function TPESectionBase.GetAllocatedSize: uint32;
begin
  Result := Length(FMem);
end;

function TPESectionBase.GetEndRawOffset: uint32;
begin
  Result := Self.FRawOffset + Self.FRawSize;
end;

function TPESectionBase.IsCode: boolean;
begin
  Result := (Flags and IMAGE_SCN_CNT_CODE) <> 0;
end;

function TPESectionBase.LoadDataFromStream(AStream: TStream): boolean;
begin
  Result := LoadDataFromStreamEx(AStream, FRawOffset, FRawSize);
end;

function TPESectionBase.LoadDataFromStreamEx(AStream: TStream;
  ARawOffset, ARawSize: uint32): boolean;
var
  cnt: uint32;
begin
  if (ARawOffset = 0) or (ARawSize = 0) then
    Exit(false); // Bad args.

  if not StreamSeek(AStream, ARawOffset) then
    Exit(false); // Can't find position in file.

  if ARawSize > GetAllocatedSize then
    ARawSize := GetAllocatedSize;

  cnt := AStream.Read(Mem^, ARawSize);
  if cnt = 0 then
  begin
    ClearData;
    if Assigned(FMsg) then
      FMsg.Write('Section %s has no raw data.', [FName]);
  end
  else if (cnt <> ARawSize) then
  begin
    if Assigned(FMsg) then
      FMsg.Write
        ('Section %s has less raw data than header declares: 0x%x instead of 0x%x.',
        [FName, cnt, ARawSize]);
    if Assigned(FMsg) then
      FMsg.Write('Actual raw size was loaded.');
  end;
  Exit(True);
end;

function TPESectionBase.NameAsHex: string;
var
  bytes: array [0 .. IMAGE_SIZEOF_SHORT_NAME - 1] of byte;
  i, len: integer;
begin
  fillchar(bytes[0], IMAGE_SIZEOF_SHORT_NAME, 0);
  len := Min(Length(name), IMAGE_SIZEOF_SHORT_NAME);
  if len > 0 then
    for i := 0 to len - 1 do
      bytes[i] := byte(name.Chars[i]);

  Result := format('%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x', [
    bytes[0],
    bytes[1],
    bytes[2],
    bytes[3],
    bytes[4],
    bytes[5],
    bytes[6],
    bytes[7]
    ]);
end;

function TPESectionBase.SaveDataToStream(AStream: TStream): boolean;
begin
{$WARN COMPARING_SIGNED_UNSIGNED OFF}
  Result := false;
  if (FMem = nil) or (FRawSize = 0) then
  begin
    if Assigned(FMsg) then
      FMsg.Write('No data to save.');
    Exit;
  end;
  Result := AStream.Write(Mem^, FRawSize) = FRawSize;
{$WARN COMPARING_SIGNED_UNSIGNED ON}
end;

procedure TPESectionBase.Resize(NewSize: uint32);
begin
  FRawSize := NewSize;
  FVSize := NewSize;
  SetAllocatedSize(NewSize);
end;

end.


text:
unit PE.Imports.Lib;

interface

uses
  System.Classes,
  System.SysUtils,

  PE.Common,
  PE.Imports.Func;

type
  TPEImportLibrary = class
  private
    FName: String; // imported library name
    FBound: Boolean;
    FFunctions: TPEImportFunctions;
    FTimeDateStamp: uint32;
    FOriginal: boolean;
    procedure CheckAddingToOriginalLib;
  public
    // Relative address of IAT region for this library.
    // It is address of first word in array of words (4/8 bytes) corresponding
    // to each imported function in same order as in Functions list.
    //
    // Each RVA is patched by loader if it's mapped into process memory.
    //
    // If image is not bound loader get address of function and write it at RVA.
    // If image is bound nothing changed because value at RVA is already set.
    //
    // This value is modified when import directory parsed on loading or
    // when import directory is rebuilt.
    IatRva: TRVA;

    constructor Create(const AName: String; Bound: Boolean = False; Original: Boolean = False);
    destructor Destroy; override;

    function NewFunction(const Name: string): TPEImportFunction; overload;
    function NewFunction(Ordinal: uint16): TPEImportFunction; overload;

    property Name: String read FName;

    // List of imported functions.
    // Order must be kept to match array of words at IatRva.
    property Functions: TPEImportFunctions read FFunctions;

    property Bound: Boolean read FBound;
    property TimeDateStamp: uint32 read FTimeDateStamp write FTimeDateStamp;

    // True if it is library parsed from executable.
    // You can't add new functions to this library, because IAT must stay untouched.
    // Add new library instead.
    property Original: boolean read FOriginal;
  end;

implementation

{ TImportLibrary }

constructor TPEImportLibrary.Create(const AName: String; Bound: Boolean; Original: Boolean);
begin
  inherited Create;
  FFunctions := TPEImportFunctions.Create;
  FName := AName;
  FBound := Bound;
  FOriginal := Original;
end;

destructor TPEImportLibrary.Destroy;
begin
  FFunctions.Free;
  inherited;
end;

procedure TPEImportLibrary.CheckAddingToOriginalLib();
begin
  if (Original) then
    raise Exception.Create('You can''t add new function to original library.');
end;

function TPEImportLibrary.NewFunction(const Name: string): TPEImportFunction;
begin
  CheckAddingToOriginalLib();
  Result := TPEImportFunction.Create(Name);
  FFunctions.Add(Result);
end;

function TPEImportLibrary.NewFunction(Ordinal: uint16): TPEImportFunction;
begin
  CheckAddingToOriginalLib();
  Result := TPEImportFunction.Create('', Ordinal);
  FFunctions.Add(Result);
end;

end.


text:
unit PE.Parser.Import;

interface

uses
  System.Generics.Collections,
  System.SysUtils,

  PE.Common,
  PE.Types,
  PE.Types.Imports,
  PE.Types.FileHeader,
  PE.Imports,
  PE.Imports.Func,
  PE.Imports.Lib,
  PE.Utils;

type
  TPEImportParser = class(TPEParser)
  public
    function Parse: TParserResult; override;
  end;

implementation

uses
  PE.Types.Directories,
  PE.Image;

{ TPEImportParser }

function TPEImportParser.Parse: TParserResult;
type
  TImpDirs = TList<TImportDirectoryTable>;
  TILTs = TList<TImportLookupTable>;
var
  dir: TImageDataDirectory;
  bIs32: boolean;
  dq: uint64;
  sizet: byte;
  IDir: TImportDirectoryTable;
  IATRVA: uint64;
  PATCHRVA: uint64; // place where loader will put new address
  IDirs: TImpDirs;
  ILT: TImportLookupTable;
  ILTs: TILTs;
  ImpFn: TPEImportFunction;
  Lib: TPEImportLibrary;
  PE: TPEImage;
  LibraryName: string;
  dwLeft: uint32;
  bEmptyLastDirFound: boolean;
  IDirNumber: integer;
begin
  PE := TPEImage(FPE);

  result := PR_ERROR;
  IDirs := TImpDirs.Create;
  ILTs := TILTs.Create;
  try
    PE.Imports.Clear;

    bIs32 := PE.Is32bit;
    sizet := PE.ImageBits div 8;

    // If no imports, it's ok.
    if not PE.DataDirectories.Get(DDIR_IMPORT, @dir) then
      exit(PR_OK);
    if dir.IsEmpty then
      exit(PR_OK);

    // Seek import dir.
    if not PE.SeekRVA(dir.VirtualAddress) then
      exit;

    // Read import descriptors.
    dwLeft := dir.Size;
    bEmptyLastDirFound := false;
    while dwLeft >= sizeof(IDir) do
    begin
      // Read IDir.
      if not PE.ReadEx(@IDir, sizeof(IDir)) then
        exit;

      if IDir.IsEmpty then // it's last dir
      begin
        bEmptyLastDirFound := true;
        break;
      end;

      // Check RVA.
      if not(PE.RVAExists(IDir.NameRVA)) then
      begin
        PE.Msg.Write(SCategoryImports, 'Bad RVAs in directory. Imports are incorrect.');
        exit;
      end;

      IDirs.Add(IDir); // add read dir

      dec(dwLeft, sizeof(IDir));
    end;

    if IDirs.Count = 0 then
    begin
      PE.Msg.Write(SCategoryImports, 'No directories found.');
      exit;
    end;

    if not bEmptyLastDirFound then
    begin
      PE.Msg.Write(SCategoryImports, 'No last (empty) directory found.');
    end;

    // Parse import descriptors.
    IDirNumber := -1;
    for IDir in IDirs do
    begin
      inc(IDirNumber);

      ILTs.Clear;

      // Read library name.
      if (not PE.SeekRVA(IDir.NameRVA)) then
      begin
        PE.Msg.Write(SCategoryImports, 'Library name RVA not found (0x%x) for dir # %d.', [IDir.NameRVA, IDirNumber]);
        Continue;
      end;

      LibraryName := PE.ReadAnsiString;

      if LibraryName.IsEmpty then
      begin
        PE.Msg.Write(SCategoryImports, 'Library # %d has empty name.', [IDirNumber]);
        Continue;
      end;

      PATCHRVA := IDir.FirstThunk;
      if PATCHRVA = 0 then
      begin
        PE.Msg.Write(SCategoryImports, 'Library # %d (%s) has NULL patch RVA.', [IDirNumber, LibraryName]);
        break;
      end;

      if IDir.ImportAddressTable <> 0 then
        IATRVA := IDir.ImportAddressTable
      else
        IATRVA := IDir.ImportLookupTableRVA;

      if IATRVA = 0 then
      begin
        PE.Msg.Write(SCategoryImports, 'Library # %d (%s) has NULL IAT RVA.', [IDirNumber, LibraryName]);
        break;
      end;

      // Lib will be created just in time.
      Lib := nil;

      // Read IAT elements.
      while PE.SeekRVA(IATRVA) do
      begin
        if not PE.ReadWordEx(0, @dq) then
        begin
          // Failed to read word and not null yet reached.
          FreeAndNil(Lib);
          PE.Msg.Write(SCategoryImports, 'Bad directory # %d. Skipped.', [IDirNumber]);
          break;
        end;

        if dq = 0 then
          break;

        ILT.Create(dq, bIs32);

        ImpFn := TPEImportFunction.CreateEmpty;

        // By ordinal.
        if ILT.IsImportByOrdinal then
        begin
          ImpFn.Ordinal := ILT.OrdinalNumber;
          ImpFn.Name := '';
        end

        // By name.
        else if PE.SeekRVA(ILT.HintNameTableRVA) then
        begin
          dq := 0;
          PE.ReadEx(@dq, 2);
          ImpFn.Name := PE.ReadAnsiString;
        end;

        if not assigned(Lib) then
        begin
          // Create lib once in loop.
          // Added after loop (if not discarded).
          Lib := TPEImportLibrary.Create(LibraryName, IDir.IsBound, True);
          Lib.TimeDateStamp := IDir.TimeDateStamp;
          Lib.IATRVA := IATRVA;
        end;

        Lib.Functions.Add(ImpFn);

        inc(IATRVA, sizet); // next item
        inc(PATCHRVA, sizet);
      end;

      // If lib is generated, add it.
      if assigned(Lib) then
        PE.Imports.Add(Lib);

    end;

    result := PR_OK;

  finally
    IDirs.Free;
    ILTs.Free;
  end;
end;

end.


text:
unit PE.Msg;

interface

type
  TMsgProc = procedure(Text: PWideChar); stdcall;

  TMsgMgr = record
  private
    FMsgProc: TMsgProc;
  public
    constructor Create(AMsgProc: TMsgProc);

    procedure Write(const AText: UnicodeString); overload;
    procedure Write(const AFmt: UnicodeString; const AArgs: array of const); overload;

    procedure Write(const Category: string; AText: UnicodeString); overload;
    procedure Write(const Category: string; AFmt: UnicodeString; const AArgs: array of const); overload;
  end;

  PMsgMgr = ^TMsgMgr;

implementation

uses
  System.SysUtils;

{ TMessageMgr }

procedure TMsgMgr.Write(const AText: UnicodeString);
begin
  if Assigned(FMsgProc) then
    FMsgProc(PWideChar(AText));
end;

constructor TMsgMgr.Create(AMsgProc: TMsgProc);
begin
  FMsgProc := AMsgProc;
end;

procedure TMsgMgr.Write(const AFmt: UnicodeString; const AArgs: array of const);
begin
  Write(Format(AFmt, AArgs));
end;

procedure TMsgMgr.Write(const Category: string; AText: UnicodeString);
begin
  write(Format('[%s] %s', [Category, AText]));
end;

procedure TMsgMgr.Write(const Category: string; AFmt: UnicodeString;
  const AArgs: array of const);
begin
  write(Category, Format(AFmt, AArgs));
end;

end.