HenryEx icon

The Letter horror visual novel - xml chapter parser QuickBMS

HenryEx | PRO | 09/06/17 04:30:12 AM UTC | 0 ⭐ | 536 👁️ | Never ⏰ | []
text |

29.83 KB

|

None

|

0 👍

/

0 👎

# Script to parse XML chapter data from The Letter
# as CSV tables for cleaner data overview
# or as text files for better readability
#
# Written by HenryEx
#
# script for QuickBMS http://quickbms.aluigi.org
 # script options
  set TO_CSV byte 1    # export as CSV
  set TO_TEXT byte 1   # export as continuous text file
  set SLIMTEXT byte 0  # slim down text export
  # set misc. standard strings / variables
  get FILENAME basename 0
  get FILESIZE asize 0
  set TITLE string ""
  set CHAPTER string ""
  set CHNAME string ""
  set BGROUND string ""
  set BGM string ""
  set NCHAPTER string ""
  set CSV string "\";\""   # set the csv string to ";"
  set LINEBREAK binary "\"\x0D\x0A\""   # end with ", linebreak, start with "
  set CR binary "\x0D"     # Carriage Return
  set LF binary "\x0A"     # Line Feed
  set CRLF short 2573      # CR+LF
  set INDENT string "  "
  set CSTACK long 0        # condition stack
  get XMLHEADER line 0
  if XMLHEADER != "<?xml version="1.0"?>"
    print "[!] Error: incorrect XML header found! Exiting..."
    CleanExit
  endif
  findloc STARTITEMS string "<items>" 0  # offset of start bracket for items
# findloc ENDITEMS string "</items>" 0  # offset of end bracket for items
 #setup virtual memory files
  math TMP = 1000
  math TMP *= 0x8000  # 32 MB
  if TO_CSV != 0
    log MEMORY_FILE 0 0
    putvarchr MEMORY_FILE TMP 0   # improves the speed with pre-allocation
    log MEMORY_FILE 0 0           # reset the position and size of the file
    set MBEGIN string "\""        # leading " 
    putct MBEGIN string -1 MEMORY_FILE
  endif
  if TO_TEXT != 0
    log MEMORY_FILE2 0 0
    putvarchr MEMORY_FILE2 TMP 0   # improves the speed with pre-allocation
    log MEMORY_FILE2 0 0           # reset the position and size of the file
    putarray 2 20 0                # pre-allocate condition index array
    putarray 2 0 0
  endif
 # set up actions array
  putarray 0 48 0  # pre-allocate
  putarray 0 0 "none"
  putarray 0 1 "say"
  putarray 0 2 "charadd"
  putarray 0 3 "question"
  putarray 0 4 "choice"
  putarray 0 5 "condition"
  putarray 0 6 "else"
  putarray 0 7 "conditionend"
  putarray 0 8 "gotochapter"
  putarray 0 9 "charmove"
  putarray 0 10 "chardel"
  putarray 0 11 "bgchange"
  putarray 0 12 "fadeout"
  putarray 0 13 "fadein"
  putarray 0 14 "wait"
  putarray 0 15 "playsound"
  putarray 0 16 "changebgm"
  putarray 0 17 "playsoundloop"
  putarray 0 18 "stopsoundloop"
  putarray 0 19 "charscale"
  putarray 0 20 "charemote"
  putarray 0 21 "portraitchange"
  putarray 0 22 "bgmove"
  putarray 0 23 "bgscale"
  putarray 0 24 "quicktime"
  putarray 0 25 "bgaction"
  putarray 0 26 "gameover"
  putarray 0 27 "checkpoint"
  putarray 0 28 "camerashake"
  putarray 0 29 "galleryunlock"
  putarray 0 30 "achievement"
  putarray 0 31 "addphonecall"
  putarray 0 32 "removephonecall"
  putarray 0 33 "playeffects"
  putarray 0 34 "unlockjournal"
  putarray 0 35 "playvoice"
  putarray 0 36 "unlockbranchtreenode"
  putarray 0 37 "setvariable"
  putarray 0 38 "charanim"
  putarray 0 39 "showgamenotification"
  putarray 0 40 "chardirection"
  putarray 0 41 "charchangearm"
  putarray 0 42 "charchangepose"
  putarray 0 43 "addfocusobject"
  putarray 0 44 "removefocusobject"
  putarray 0 45 "unlockending"
  putarray 0 46 "unlockrelationship"
  putarray 0 47 "eyeanim"
  putarray 0 48 "showingametitle"
 # set up eyeanim array
  putarray 1 4 0  # pre-allocate
  putarray 1 0 "DisableOverride"
  putarray 1 1 "Eye00Default"
  putarray 1 2 "Eye01Left"
  putarray 1 3 "Eye02Right"
  putarray 1 4 "Eye03Dilated"
  # ========================================
 # process first lines
for i = 0 < 1
  get DATA line 0
  savepos OFFSET 0  # offset of next line
   if OFFSET >= STARTITEMS
    math OFFSET = STARTITEMS
    math i = 1  # stop reading header lines
  endif
   if DATA != "" # parse other header items
    if DATA & "</Title>"
      string DATA > "</Title>"  # clip off end tag
      string DATA | "<Title>"   # clip off start tag
      set TITLE string DATA
    elif DATA & "</Chapter>"
      string DATA > "</Chapter>"  # clip off end tag
      string DATA | "<Chapter>"   # clip off start tag
      set CHAPTER string DATA
    elif DATA & "</ChapterName>"
      string DATA > "</ChapterName>"  # clip off end tag
      string DATA | "<ChapterName>"   # clip off start tag
      set CHNAME string DATA
    elif DATA & "</Background>"
      string DATA > "</Background>"  # clip off end tag
      string DATA | "<Background>"   # clip off start tag
      set BGROUND string DATA
    elif DATA & "</BGM>"
      string DATA > "</BGM>"  # clip off end tag
      string DATA | "<BGM>"   # clip off start tag
      set BGM string DATA
    elif DATA & "</NextChapter>"
      string DATA > "</NextChapter>"  # clip off end tag
      string DATA | "<NextChapter>"   # clip off start tag
      set NCHAPTER string DATA
    endif
  endif
next
 if TO_CSV != 0
# write header columns, row 1
/*
  string HEADER = "Chapter "
  string HEADER + CHAPTER
  string HEADER + ", "
  string HEADER + CHNAME
  string HEADER + ", Title: "
  string HEADER + TITLE
  string HEADER + ", BG: "
  string HEADER + BGROUND
  string HEADER + ", BGM: "
  string HEADER + BGM
  string HEADER + ", next: "
  string HEADER + NCHAPTER
  putct HEADER string -1 MEMORY_FILE
  putct LINEBREAK string -1 MEMORY_FILE
*/
 # write header columns, row 2
  putct "Idx" string -1 MEMORY_FILE
  putct CSV string -1 MEMORY_FILE
  putct "Action" string -1 MEMORY_FILE
  putct CSV string -1 MEMORY_FILE
  putct "Name" string -1 MEMORY_FILE
  putct CSV string -1 MEMORY_FILE
  putct "VN Text" string -1 MEMORY_FILE
  putct CSV string -1 MEMORY_FILE
  putct "Variable1" string -1 MEMORY_FILE
  putct CSV string -1 MEMORY_FILE
  putct "Value" string -1 MEMORY_FILE
  putct LINEBREAK string -1 MEMORY_FILE
endif
 if TO_TEXT != 0
  putct "Chapter " string -1 MEMORY_FILE2
  putct CHAPTER string -1 MEMORY_FILE2
  putct ": " string -1 MEMORY_FILE2
  putct CHNAME string -1 MEMORY_FILE2
  put CRLF short MEMORY_FILE2
  if TITLE != ""
    putct "Title: " string -1 MEMORY_FILE2
    putct TITLE string -1 MEMORY_FILE2
    put CRLF short MEMORY_FILE2
  endif
  if BGROUND != ""
    putct "Background: " string -1 MEMORY_FILE2
    putct BGROUND string -1 MEMORY_FILE2
    put CRLF short MEMORY_FILE2
  endif
  if BGM != ""
    putct "BGM: " string -1 MEMORY_FILE2
    putct BGM string -1 MEMORY_FILE2
    put CRLF short MEMORY_FILE2
  endif
  if NCHAPTER != ""
    putct "Next default chapter: " string -1 MEMORY_FILE2
    putct NCHAPTER string -1 MEMORY_FILE2
    put CRLF short MEMORY_FILE2
  endif
  put CRLF short MEMORY_FILE2
endif
  # ========================================
 # go to offset after <items>
  math OFFSET + 7
  goto OFFSET 0
 # process VNItems
for i = 0 < 1
  # set up default field values
  math ACTIONID = 0
  string VNACTION = "?"
  string INDEX = "?"
  string DIRECTION = ""
  string SIZE = ""
  string SPEED = ""
  string TRANSITION = ""
  string VAR1 = ""
  string CNAME = ""
  string AUTONEXT = ""
  string POSX = ""
  string POSY = ""
  string FADE = ""
  string VNTEXT = ""
  string ANIMATION = ""
  string EMOTION = ""
  string ARMPOSE = ""
  string EYEANIMID = ""
  string EYEANIMATION = ""
  string XMLVAL = ""
 /*
  # get VNItem  line
  get DATA line 0
   if DATA & "</items>"
    string DATA > "</items>"
    math i + 1  # stop
  endif
*/
   # alternate data fetch across lines
  findloc TAG_OFF string "<VNItem" 0 ""
  if TAG_OFF != ""
    findloc TAG_END string "</VNItem>"
    xmath TAG_SZ "TAG_END + 9 - TAG_OFF"
    goto TAG_OFF 0
    getdstring DATA TAG_SZ 0
  else
    math i = 1  # no more lines found, stop
    continue  # end for loop
  endif
   # get action id
  string TEMP = DATA
  string TEMP 0| "id=\""
  string TEMP 0% "\""
  if TEMP != ""
    math ACTIONID = TEMP
    getarray VNACTION 0 ACTIONID
  endif
   # get index
  string TEMP = DATA
  string TEMP 0| "idx=\""
  string TEMP 0% "\""
  if TEMP != ""
    math INDEX = TEMP
  endif
   # abort if we got bogus data
  if ACTIONID > 48
    print "[!] Error: action ID %ACTIONID% on line %INDEX% out of bounds! Exiting..."
    CleanExit
  endif
   # get direction
  string TEMP = DATA
  string TEMP 0| "direction=\""
  string TEMP 0% "\""
  if TEMP != ""
    math DIRECTION = TEMP
  endif
   # get size (float)
  string TEMP = DATA
  string TEMP 0| "size=\""
  string TEMP 0% "\""
  if TEMP != ""
    set SIZE string TEMP
  endif
   # get speed (float)
  string TEMP = DATA
  string TEMP 0| "speed=\""
  string TEMP 0% "\""
  if TEMP != ""
    set SPEED string TEMP
  endif
   # get transition
  string TEMP = DATA
  string TEMP 0| "transition=\""
  string TEMP 0% "\""
  if TEMP != ""
    callfunction CLEAN_XML_STR 1
    set TRANSITION string TEMP
  endif
   # get variable1
  string TEMP = DATA
  string TEMP 0| "variable1=\""
  string TEMP 0% "\""
  if TEMP != ""
    callfunction CLEAN_XML_STR 1
    set VAR1 string TEMP
  endif
   # get name
  string TEMP = DATA
  string TEMP 0| "name=\""
  string TEMP 0% "\""
  if TEMP != ""
    callfunction CLEAN_XML_STR 1
    set CNAME string TEMP
  endif
   # get autonext (float)
  string TEMP = DATA
  string TEMP 0| "autonext=\""
  string TEMP 0% "\""
  if TEMP != ""
    set AUTONEXT string TEMP
  endif
   # get posX (float)
  string TEMP = DATA
  string TEMP 0| "posX=\""
  string TEMP 0% "\""
  if TEMP != ""
    set POSX string TEMP
  endif
   # get posY (float)
  string TEMP = DATA
  string TEMP 0| "posY=\""
  string TEMP 0% "\""
  if TEMP != ""
    set POSY string TEMP
  endif
   # get fade (bool)
  string TEMP = DATA
  string TEMP 0| "fade=\""
  string TEMP 0% "\""
  if TEMP != ""
    set FADE string TEMP
  endif
   # get VN text
  string TEMP = DATA
  string TEMP 0| "vntext=\""
  string TEMP 0% "\""
  if TEMP != ""
    callfunction CLEAN_XML_STR 1
    set VNTEXT string TEMP
  endif
   # get animation
  string TEMP = DATA
  string TEMP 0| "animation=\""
  string TEMP 0% "\""
  if TEMP != ""
    callfunction CLEAN_XML_STR 1
    set ANIMATION string TEMP
  endif
   # get emotion
  string TEMP = DATA
  string TEMP 0| "emotion=\""
  string TEMP 0% "\""
  if TEMP != ""
    callfunction CLEAN_XML_STR 1
    set EMOTION string TEMP
  endif
   # get arm pose
  string TEMP = DATA
  string TEMP 0| "armpose=\""
  string TEMP 0% "\""
  if TEMP != ""
    callfunction CLEAN_XML_STR 1
    set ARMPOSE string TEMP
  endif
   # get eye animation
  string TEMP = DATA
  string TEMP 0| "eyeanim=\""
  string TEMP 0% "\""
  if TEMP != ""
    math EYEANIMID = TEMP
  endif
   # get value
  string TEMP = DATA
  string TEMP > "</VNItem>"
  string TEMP 0| ">"
  if TEMP != ""
    callfunction CLEAN_XML_STR 1
    set XMLVAL string TEMP
  endif
   # put values into table
  if TO_CSV != 0
    putct INDEX string -1 MEMORY_FILE
    putct CSV string -1 MEMORY_FILE
    putct VNACTION string -1 MEMORY_FILE
    putct CSV string -1 MEMORY_FILE
    putct CNAME string -1 MEMORY_FILE
    putct CSV string -1 MEMORY_FILE
    putct VNTEXT string -1 MEMORY_FILE
    putct CSV string -1 MEMORY_FILE
    putct VAR1 string -1 MEMORY_FILE
    putct CSV string -1 MEMORY_FILE
    putct XMLVAL string -1 MEMORY_FILE
    putct LINEBREAK string -1 MEMORY_FILE
  endif
    # parse values into text file
  if TO_TEXT != 0
    if INDEX > 9999   # whoa what
      string LINETEXT = "[!]Error: Index of 10k and above unsupported!"
      putct LINETEXT string -1 MEMORY_FILE2
      math i = 1      # stop everything
      continue        # abort, abort
    endif
     string LINETEXT p "%.4i: " INDEX  # get 4 digit idx
    for j = CSTACK > 0
      string LINETEXT + INDENT  # indent text depending on conditions stack
    prev j
     # mod string for every action ID case
    if ACTIONID == 0     # none
    # print "Parse action 0: none @ idx %INDEX%"
      string LINETEXT + "[NONE]"
      if CNAME != ""
        string LINETEXT + " Name: "
        string LINETEXT + CNAME
      endif
      if XMLVAL != ""
        string LINETEXT + " Value: "
        string LINETEXT + XMLVAL
      endif
      if VNTEXT != ""
        string LINETEXT + " VNText: "
        string LINETEXT + VNTEXT
      endif
    elif ACTIONID == 1   # say
    # print "Parse action 1: say @ idx %INDEX%"
      set SPEAKER string CNAME
      if SPEAKER == "None"
        string SPEAKER = ""
      endif
      if VAR1 != ""
        set PORTRAIT1 string VAR1
        set PORTRAIT2 string VAR1
        string PORTRAIT1 % "/"   # 1st half of portrait
        string PORTRAIT2 0| "/"  # 2nd half of portrait or ""
        if PORTRAIT2 == "None"
          string PORTRAIT2 = ""
        endif
      else
        string PORTRAIT1 = "None"
        string PORTRAIT2 = ""
      endif
      set PORTRAIT string PORTRAIT1
      string PORTRAIT + PORTRAIT2
      if SLIMTEXT == 0
        string LINETEXT + "{"
        string LINETEXT + "PORTRAIT"
        string LINETEXT + "} \""
        string LINETEXT + VNTEXT
        string LINETEXT + "\" ("
        if SPEAKER != ""
          string LINETEXT + "as "
          string LINETEXT + SPEAKER
          string LINETEXT + ", "
        endif
        string LINETEXT + "voice: "
        string LINETEXT + XMLVAL
        string LINETEXT + ")"
      else
        string VNTEXT replace "''" "\""  # double apostrophe => quotation marks
        if PORTRAIT == "Others" && SPEAKER != ""
          string LINETEXT + "[b]"
          string LINETEXT + SPEAKER
          string LINETEXT + "[/b]"
        elif SPEAKER == "???"
          string LINETEXT + "[b]"
          string LINETEXT + SPEAKER
          string LINETEXT + "[/b]"
        else
          string LINETEXT + "{"
          string LINETEXT + "PORTRAIT"
          string LINETEXT + "}"
        endif
        string LINETEXT + ": "
        string LINETEXT + VNTEXT
      endif
    elif ACTIONID == 2   # charadd
    # print "Parse action 2: charadd @ idx %INDEX%"
      string LINETEXT + "[C]+ >'"
      string LINETEXT + XMLVAL
      string LINETEXT + "' added"
      if SLIMTEXT == 0
        string LINETEXT + " @ pos ("
        string LINETEXT + POSX
        string LINETEXT + ", "
        string LINETEXT + POSY
        string LINETEXT + "), "
        if DIRECTION == 1
          string LINETEXT + "facing left"
        elif DIRECTION == -1
          string LINETEXT + "facing right"
        else
          string LINETEXT + "direction "
          if DIRECTION != "" 
            string LINETEXT + DIRECTION
          else
            string LINETEXT + "???"
          endif
        endif
      endif
      string LINETEXT + ", emoting "
      string LINETEXT + EMOTION
      if EYEANIMID != ""
        string LINETEXT + " with eyes #"
        string LINETEXT + EYEANIMID
        if EYEANIMID < 5
          getarray EYEANIMATION 1 EYEANIMID
          string LINETEXT + ": "
          string LINETEXT + EYEANIMATION
        else
          print "[!] Error: Unknown eye animation %EYEANIMID% at idx %INDEX%"
        endif
      endif
      if SLIMTEXT == 0
        string LINETEXT + ", pose "
        string LINETEXT + ANIMATION
        string LINETEXT + ", arms "
        string LINETEXT + ARMPOSE
      endif
      if DIRECTION != ""
        if DIRECTION != 1 && DIRECTION != -1
          string LINETEXT + " [!]Error: Unknown direction!"
        endif
      endif
      if EYEANIMID != ""
        if EYEANIMID > 4
          string LINETEXT + " [!]Error: Unknown eye animation!"
        endif
      endif
    elif ACTIONID == 3   # question
    # print "Parse action 3: question @ idx %INDEX%"
      string LINETEXT + "[Q] \""
      string LINETEXT + VNTEXT
      string LINETEXT + "\""
    elif ACTIONID == 4   # choice
    # print "Parse action 4: choice @ idx %INDEX%"
      string LINETEXT + " [A] \""
      string LINETEXT + VNTEXT
      string LINETEXT + "\" --> "
      set TEMP string VAR1
      string TEMP R "_" ", "
      string LINETEXT + TEMP
    elif ACTIONID == 5   # condition
    # print "Parse action 5: condition @ idx %INDEX%"
      if XMLVAR == ""
        print "[!]Error: Condition missing on idx %INDEX%!"
        string LINETEXT P "[!]Error: Condition missing on idx %INDEX%!"
      else
        math CSTACK + 1
        putarray 2 CSTACK INDEX  # put idx of condition in stack array
        string LINETEXT + "$If ( "
        set TEMP string XMLVAL
        if TEMP & "|"
          for j = 0 < 1
            set TEMP2 string TEMP
            string TEMP2 0% "|"  # str before 1st OR
            string TEMP | "|"   # str after 1st OR, rest
            if TEMP2 != ""
              string LINETEXT + TEMP2
              string LINETEXT + " OR "
            else
              string LINETEXT + TEMP
              math j = 1
            endif
          next
        elif TEMP & "&"
          for j = 0 < 1
            set TEMP2 string TEMP
            string TEMP2 0% "&"  # str before 1st AND
            string TEMP | "&"   # str after 1st AND, rest
            if TEMP2 != ""
              string LINETEXT + TEMP2
              string LINETEXT + " AND "
            else
              string LINETEXT + TEMP
              math j = 1
            endif
          next
        else
          string LINETEXT + XMLVAL
        endif
        string LINETEXT + " )"
      endif
    elif ACTIONID == 6   # else
    # print "Parse action 6: else @ idx %INDEX%"
      getarray TEMP 2 CSTACK  # get idx of current condition
      string TEMP p "%.4i" TEMP
      string LINETEXT > INDENT  # remove indentation
      string LINETEXT + "$Else @"
      string LINETEXT + TEMP
      if CSTACK < 1
        print "[!] Error: Hit ELSE before condition on idx %INDEX%!"
        string LINETEXT P "[!]Error: Hit ELSE before condition on idx %INDEX%!"
      endif
    elif ACTIONID == 7   # conditionend
    # print "Parse action 7: conditionend @ idx %INDEX%"
      getarray TEMP 2 CSTACK  # get idx of current condition
      string TEMP p "%.4i" TEMP
      string LINETEXT > INDENT  # remove indentation
      string LINETEXT + "$Endif @"
      string LINETEXT + TEMP
      if CSTACK > 0
        math CSTACK - 1
      else
        print "[!] Error: Hit END before condition on idx %INDEX%!"
        string LINETEXT P "[!]Error: Hit END before condition on idx %INDEX%!"
      endif
    elif ACTIONID == 8   # gotochapter
    # print "Parse action 8: gotochapter @ idx %INDEX%"
      string LINETEXT + "[-->] Chapter "
      string LINETEXT + XMLVAL
    elif ACTIONID == 9   # charmove
    # print "Parse action 9: charmove @ idx %INDEX%"
      string LINETEXT + ">'"
      string LINETEXT + XMLVAL
      string LINETEXT + "' moved by ("
      string LINETEXT + POSX
      string LINETEXT + ", "
      string LINETEXT + POSY
      string LINETEXT + "), speed "
      string LINETEXT + SPEED
    elif ACTIONID == 10  # chardel
    # print "Parse action 10: chardel @ idx %INDEX%"
      string LINETEXT + "[C]- >'"
      string LINETEXT + XMLVAL
      string LINETEXT + "' leaves the screen"
    elif ACTIONID == 11  # bgchange
    # print "Parse action 11: bgchange @ idx %INDEX%"
      if VAR1 == "" || VAR1 == 1
        string LINETEXT + "[BG] Fade to "
      else
        string LINETEXT + "[BG] Change to "
      endif
      string LINETEXT + XMLVAL
#   elif ACTIONID == 12  # fadeout, unused
    # print "Parse action 12: fadeout @ idx %INDEX%"
#   elif ACTIONID == 13  # fadein, unused
    # print "Parse action 13: fadein @ idx %INDEX%"
#   elif ACTIONID == 14  # wait, unused
    # print "Parse action 14: wait @ idx %INDEX%"
    elif ACTIONID == 15  # playsound
    # print "Parse action 15: playsound @ idx %INDEX%"
      string LINETEXT + "[SFX] "
      string LINETEXT + XMLVAL
      if SLIMTEXT != 0
        if XMLVAL == "GameNotificationSFX"
          string LINETEXT = ""
        endif
      endif
    elif ACTIONID == 16  # changebgm
    # print "Parse action 16: changebgm @ idx %INDEX%"
      string LINETEXT + "[MUSIC] "
      string LINETEXT + XMLVAL
      if VAR1 != ""
        string LINETEXT + " (on next BG change)"
      endif
    elif ACTIONID == 17  # playsoundloop
    # print "Parse action 17: playsoundloop @ idx %INDEX%"
      string LINETEXT + "[SFX] Play loop: "
      string LINETEXT + XMLVAL
    elif ACTIONID == 18  # stopsoundloop
    # print "Parse action 18: stopsoundloop @ idx %INDEX%"
      string LINETEXT + "[SFX] Stop loop"
#   elif ACTIONID == 19  # charscale, unused
    # print "Parse action 19: charscale @ idx %INDEX%"
    elif ACTIONID == 20  # charemote
    # print "Parse action 20: charemote @ idx %INDEX%"
      string LINETEXT + ">'"
      string LINETEXT + CNAME
      string LINETEXT + "' emotes: "
      string LINETEXT + XMLVAL
#   elif ACTIONID == 21  # portraitchange, unused
    # print "Parse action 21: portraitchange @ idx %INDEX%"
    elif ACTIONID == 22  # bgmove
    # print "Parse action 22: bgmove @ idx %INDEX%"
      string LINETEXT + "[BG] Move to pos ("
      string LINETEXT + POSX
      string LINETEXT + ", "
      string LINETEXT + POSY
      string LINETEXT + "), speed: "
      string LINETEXT + SPEED
#   elif ACTIONID == 23  # bgscale, unused
    # print "Parse action 23: bgscale @ idx %INDEX%"
    elif ACTIONID == 24  # quicktime
    # print "Parse action 24: quicktime @ idx %INDEX%"
      string LINETEXT + "[QTE] "
      string LINETEXT + XMLVAL
      string LINETEXT + " (var "
      string LINETEXT + VAR1
      string LINETEXT + ")"
    elif ACTIONID == 25  # bgaction
    # print "Parse action 25: bgaction @ idx %INDEX%"
      string LINETEXT + "[BG] Action #"
      string LINETEXT + XMLVAL
    elif ACTIONID == 26  # gameover
    # print "Parse action 26: gameover @ idx %INDEX%"
      string LINETEXT + "[GAME OVER]"
    elif ACTIONID == 27  # checkpoint
    # print "Parse action 27: checkpoint @ idx %INDEX%"
      string LINETEXT + "[CHECKPOINT]"
    elif ACTIONID == 28  # camerashake
    # print "Parse action 28: camerashake @ idx %INDEX%"
      if SLIMTEXT == 0
        string LINETEXT + "[SHAKE] Duration "
        string LINETEXT + SPEED
        string LINETEXT + ", Intensity "
        string LINETEXT + SIZE
      else
        string LINETEXT = ""
      endif
#   elif ACTIONID == 29  # galleryunlock, unused
    # print "Parse action 29: galleryunlock @ idx %INDEX%"
    elif ACTIONID == 30  # achievement
    # print "Parse action 30: achievement @ idx %INDEX%"
      string LINETEXT + "[ACHIEVEMENT] "
      string TEMP = VNTEXT
      string TEMP | ": "
      if SLIMTEXT == 0
        string LINETEXT + TEMP
        string LINETEXT + " (id: #"
        string LINETEXT + VAR1
        string LINETEXT + ")"
      else
        string LINETEXT + "[b]"
        string LINETEXT + TEMP
        string LINETEXT + "[/b]"
      endif
    elif ACTIONID == 31  # addphonecall
    # print "Parse action 31: addphonecall @ idx %INDEX%"
      if CNAME != ""
        set CALLNAME string CNAME
        set CALLIMG string CNAME
        string CALLNAME % "/"   # 1st half of string
        string CALLIMG 0| "/"  # 2nd half of string or ""
        string LINETEXT + "[PHONE] Add "
        string LINETEXT + XMLVAL
        string LINETEXT + ", caller "
        string LINETEXT + CALLNAME
        string LINETEXT + ", image "
        string LINETEXT + CALLIMG
        string LINETEXT + ", type: "
        if VAR1 == 0
          string LINETEXT + "Talking"
        elif VAR1 == 1
          string LINETEXT + "Incoming"
        elif VAR1 == 2
          string LINETEXT + "Calling"
        else
          string LINETEXT + "[!]Error: Unknown phonecall type!"
        endif
      else
        string LINETEXT + "[!]Error: Phone call with undefined recipient!"
      endif
    elif ACTIONID == 32  # removephonecall
    # print "Parse action 32: removephonecall @ idx %INDEX%"
      string LINETEXT + "[PHONE] Remove phonecall"
#   elif ACTIONID == 33  # playeffects, unused
    # print "Parse action 33: playeffects @ idx %INDEX%"
    elif ACTIONID == 34  # unlockjournal
    # print "Parse action 34: unlockjournal @ idx %INDEX%"
      string LINETEXT + "[JOURNAL] Unlock section "
      string LINETEXT + XMLVAL
      string LINETEXT + " in "
      if VAR1 == 1
        string LINETEXT + "Timeline"
      elif VAR1 == 3
        string LINETEXT + "Profiles"
      elif VAR1 == 2
        string LINETEXT + "E. Mansion"
      else
        print "[!] Error: Unknown journal ID %VAR1% on idx %INDEX%!"
        string LINETEXT P "[!]Error: Unknown journal ID %VAR1% on idx %INDEX%!"
      endif
#   elif ACTIONID == 35  # playvoice, unused
    # print "Parse action 35: playvoice @ idx %INDEX%"
    elif ACTIONID == 36  # unlockbranchtreenode
    # print "Parse action 36: unlockbranchtreenode @ idx %INDEX%"
      if SLIMTEXT == 0
        string LINETEXT + "Unlock branch tree node #"
        string LINETEXT + XMLVAL
        string LINETEXT + " in route "
        string LINETEXT + VAR1
      else
        string LINETEXT = ""
      endif
    elif ACTIONID == 37  # setvariable
    # print "Parse action 37: setvariable @ idx %INDEX%"
      set TEMP string XMLVAL
      if TEMP & "gameData"
        string TEMP | "gameData"
        string LINETEXT + "[SETVAR] persistent var "
      else
        string LINETEXT + "[SETVAR] current var "
      endif
      string LINETEXT + TEMP
    elif ACTIONID == 38  # charanim
    # print "Parse action 38: charanim @ idx %INDEX%"
      string LINETEXT + ">'"
      string LINETEXT + CNAME
      string LINETEXT + "' animates with: "
      string LINETEXT + XMLVAL
    elif ACTIONID == 39  # showgamenotification
    # print "Parse action 39: showgamenotification @ idx %INDEX%"
      if VNTEXT == "Story Updated"
        string LINETEXT + "[BUTTERFLY]"
      elif VNTEXT & "Journal" && SLIMTEXT != 0
        string LINETEXT = ""
      else
        string LINETEXT + "[NOTE] "
        if SLIMTEXT != 0
          string LINETEXT + "[b]"
        endif
        string LINETEXT + "'"
        string LINETEXT + VNTEXT
        string LINETEXT + "'"
        if SLIMTEXT != 0
          string LINETEXT + "[/b]"
        else
          string LINETEXT + " (icon "
          string LINETEXT + XMLVAL
          string LINETEXT + ")"
        endif
      endif
#   elif ACTIONID == 40  # chardirection, unused
    # print "Parse action 40: chardirection @ idx %INDEX%"
    elif ACTIONID == 41  # charchangearm
    # print "Parse action 41: charchangearm @ idx %INDEX%"
      string LINETEXT + ">'"
      string LINETEXT + CNAME
      string LINETEXT + "' animates into arm position "
      string LINETEXT + XMLVAL
    elif ACTIONID == 42  # charchangepose
    # print "Parse action 42: charchangepose @ idx %INDEX%"
      string LINETEXT + ">'"
      string LINETEXT + CNAME
      string LINETEXT + "' animates into position "
      string LINETEXT + XMLVAL
    elif ACTIONID == 43  # addfocusobject
    # print "Parse action 43: addfocusobject @ idx %INDEX%"
      string LINETEXT + "[ITEM] Show: "
      string LINETEXT + XMLVAL
    elif ACTIONID == 44  # removefocusobject
    # print "Parse action 44: removefocusobject @ idx %INDEX%"
      string LINETEXT + "[ITEM] Hide"
    elif ACTIONID == 45  # unlockending
    # print "Parse action 45: unlockending @ idx %INDEX%"
      string LINETEXT + "[UNLOCK] Ending #"
      string LINETEXT + XMLVAL
    elif ACTIONID == 46  # unlockrelationship
    # print "Parse action 46: unlockrelationship @ idx %INDEX%"
      if SLIMTEXT == 0
        string LINETEXT + "[UNLOCK] Relationship #"
        string LINETEXT + XMLVAL
      else
        string LINETEXT = ""
      endif
    elif ACTIONID == 47  # eyeanim
    # print "Parse action 47: eyeanim @ idx %INDEX%"
      string LINETEXT + ">'"
      string LINETEXT + CNAME
      string LINETEXT + "' eye animation #"
      string LINETEXT + XMLVAL
      if XMLVAL < 5
        string LINETEXT + ": "
        getarray EYEANIMATION 1 XMLVAL
        string LINETEXT + EYEANIMATION
      endif
#   elif ACTIONID == 48  # showingametitle, unused
    # print "Parse action 48: showingametitle @ idx %INDEX%"
    else                 # ActionID > 48????
      print "[!] Error: action ID %ACTIONID% on line %INDEX% unassigned or out of bounds! Skipping line."
      string TEMP P "[!] Unassigned action ID %ACTIONID%."
      string LINETEXT + TEMP
    endif
     if LINETEXT != ""
      if SLIMTEXT == 0 && AUTONEXT != ""
        if AUTONEXT > 9999 && TRANSITION == "FORCE_TRANSITION"
          string LINETEXT + " [WAIT for transition finish]"
        else
          string LINETEXT + " [WAIT "
          string LINETEXT + AUTONEXT
          string LINETEXT + " seconds]"
        endif
      endif
      putct LINETEXT string -1 MEMORY_FILE2
      put CRLF short MEMORY_FILE2
    endif
  endif
 next
   # ========================================
 if TO_CSV != 0
  set FILENAME string CHAPTER
  string FILENAME + ". "
  string FILENAME + CHNAME
  string FILENAME + ", "
  string FILENAME + TITLE
  string FILENAME + ".csv"
   get MLENGTH asize MEMORY_FILE
  log FILENAME 0 MLENGTH MEMORY_FILE
endif
 if TO_TEXT != 0
  set FILENAME string CHAPTER
  string FILENAME + ". "
  string FILENAME + CHNAME
  if SLIMTEXT != 0
    string FILENAME + "_s"
  endif
  string FILENAME + ".txt"
   get MLENGTH asize MEMORY_FILE2
  log FILENAME 0 MLENGTH MEMORY_FILE2
endif
 CleanExit
 # function to replace XML escape characters in TEMP string
startfunction CLEAN_XML_STR
  string TEMP - CR  # remove carriage returns
  string TEMP - LF  # remove line feeds
  string TEMP replace "&lt;" "<"
  string TEMP replace "&gt;" ">"
  string TEMP replace "&amp;" "&"
  string TEMP replace "&quot;" "''"  # use double apostrophe to not mess up csv text
  string TEMP replace "&apos;" "'"
endfunction

Comments