'---------------------------------------------------------------'
' DungeonConverter.vbs                                          '
' Version 1.2 - April 11, 2009                                  '
' Written by Christophe Fontanel                                '
'                                                               '
' Please visit The Dungeon Master & Chaos Strikes Back          '
' Encyclopaedia at http://dmweb.free.fr/ for more information   '
'---------------------------------------------------------------'


' 1.1: Fixed bug converting DMII dungeons (map definitions), automatic output file name, single popup message after conversion, checksum is now converted and checked
' Added support for dungeons extracted from saved games (dungeons that have projectile and explosion objects)
' Checksum is: add values of all input bytes. Store result in a word.
' 1.2: Added offset variable

Option Explicit
'On Error Resume Next

Const ForReading = 1, ForWriting = 2
Const LITTLE_ENDIAN = 0, BIG_ENDIAN = 1

Dim i, j, t, buffer, Endian, EndianOK, Checksum, blnChecksumInSource, SourceChecksum, ComputedChecksum
Dim ScriptArguments, FileSystemObject
Dim SourceFileName, SourceFile, SourceFileType, SourceFileSize, DestinationFileName, DestinationFile, DestinationFileContent

' The following variables will contain the RAW contents of a dungeon file
Dim RandomGraphicsSeed, MapDataSize, NumberOfMaps, TextDataSize, StartingPartyPosition, ObjectListSize
Dim NumberOfDoors, NumberOfTeleporters, NumberOfTexts, NumberOfSensors, NumberOfGroups, NumberOfWeapons
Dim NumberOfArmours, NumberOfScrolls, NumberOfPotions, NumberOfContainers, NumberOfJunks, NumberOfProjectiles, NumberOfExplosions

Dim NumberOfColumns
Dim Offset


Function ReadWord(Number)
  Dim Char1, Char2, i

  For i = 1 To Number
    Char1 = SourceFile.Read(1)
    Char2 = SourceFile.Read(1)
    Offset = Offset + 2
    Checksum = Checksum + Asc(Char1) + Asc(Char2)
    DestinationFileContent = DestinationFileContent & Char2 & Char1

    If Endian = LITTLE_ENDIAN Then
      ReadWord = Asc(Char1) + 256 * Asc(Char2)
    Else
      ReadWord = Asc(Char1) * 256 + Asc(Char2)
    End If
  Next
End Function

Function ReadChar(Number)
  Dim i

  For i = 1 To Number
    ReadChar = SourceFile.Read(1)
    Offset = Offset + 1
    Checksum = Checksum + Asc(ReadChar)
    DestinationFileContent = DestinationFileContent & ReadChar
  Next
End Function

Sub ReadHeader
  Offset = 0
  
  RandomGraphicsSeed = ReadWord(1)
  MapDataSize = ReadWord(1)
  NumberOfMaps = Asc(ReadChar(1))
  ReadChar(1)
  TextDataSize = ReadWord(1)
  StartingPartyPosition = ReadWord(1)
  ObjectListSize = ReadWord(1)
  NumberOfDoors = ReadWord(1)
  NumberOfTeleporters = ReadWord(1)
  NumberOfTexts = ReadWord(1)
  NumberOfSensors = ReadWord(1)
  NumberOfGroups = ReadWord(1)
  NumberOfWeapons = ReadWord(1)
  NumberOfArmours = ReadWord(1)
  NumberOfScrolls = ReadWord(1)
  NumberOfPotions = ReadWord(1)
  NumberOfContainers = ReadWord(1)
  NumberOfJunks = ReadWord(1)
  ReadWord(3)
  NumberOfProjectiles = ReadWord(1)
  NumberOfExplosions = ReadWord(1)
End Sub

Sub ReadMapDefinitions
  Dim i

  NumberOfColumns = 0
  For i = 0 To NumberOfMaps - 1
    ReadWord(2)
    ReadChar(4)
    NumberOfColumns = NumberOfColumns + ((ReadWord(1) \ 2^6) And &h1F) + 1
    'MsgBox "NumberOfColumns " & NumberOfColumns
    ReadWord(3)
  Next
End Sub

Function ComputeSize
  ComputeSize = 44  ' Header size
  ComputeSize = ComputeSize + NumberOfMaps * 16
  ComputeSize = ComputeSize + NumberOfColumns * 2
  ComputeSize = ComputeSize + ObjectListSize * 2
  ComputeSize = ComputeSize + TextDataSize * 2
  ComputeSize = ComputeSize + NumberOfDoors * 4
  ComputeSize = ComputeSize + NumberOfTeleporters * 6
  ComputeSize = ComputeSize + NumberOfTexts * 4
  ComputeSize = ComputeSize + NumberOfSensors * 8
  ComputeSize = ComputeSize + NumberOfGroups * 16
  ComputeSize = ComputeSize + NumberOfWeapons * 4
  ComputeSize = ComputeSize + NumberOfArmours * 4
  ComputeSize = ComputeSize + NumberOfScrolls * 4
  ComputeSize = ComputeSize + NumberOfPotions * 4
  ComputeSize = ComputeSize + NumberOfContainers * 8
  ComputeSize = ComputeSize + NumberOfJunks * 4
  ComputeSize = ComputeSize + NumberOfProjectiles * 8
  ComputeSize = ComputeSize + NumberOfExplosions * 4
  ComputeSize = ComputeSize + MapDataSize
End Function

Sub ConvertDungeon
  Dim i

  For i = 1 To NumberOfColumns
    ReadWord(1)
  Next

  For i = 1 To ObjectListSize
    ReadWord(1)
  Next

  For i = 1 To TextDataSize
    ReadWord(1)
  Next

  For i = 1 To NumberOfDoors
    ReadWord(2)
  Next

  For i = 1 To NumberOfTeleporters
    ReadWord(3)
  Next

  For i = 1 To NumberOfTexts
    ReadWord(2)
  Next

  For i = 1 To NumberOfSensors
    ReadWord(4)
  Next

  For i = 1 To NumberOfGroups
    ReadWord(2)
    ReadChar(2)
    ReadWord(5)
  Next

  For i = 1 To NumberOfWeapons
    ReadWord(2)
  Next

  For i = 1 To NumberOfArmours
    ReadWord(2)
  Next

  For i = 1 To NumberOfScrolls
    ReadWord(2)
  Next

  For i = 1 To NumberOfPotions
    ReadWord(2)
  Next

  For i = 1 To NumberOfContainers
    ReadWord(4)
  Next

  'MsgBox "Offset before 'Junk' node data:" & Offset
  For i = 1 To NumberOfJunks
    ReadWord(2)
  Next
  'MsgBox "Offset after 'Junk' node data:" & Offset

  For i = 1 To NumberOfProjectiles
    ReadWord(2)
    ReadChar(2)
    ReadWord(1)
  Next

  For i = 1 To NumberOfExplosions
    ReadWord(2)
  Next

  ReadChar(MapDataSize)

  If blnChecksumInSource Then
    ComputedChecksum = Checksum And 65535
    SourceChecksum = ReadWord(1)

    If ComputedChecksum <> SourceChecksum Then
      EndMessage = EndMessage & vbCrLf & vbCrLf & "WARNING: The checksum value in the source dungeon is incorrect: " & vbCrLf & "Computed checksum value: " & Hex(ComputedChecksum) & vbCrLf & "Source checksum value: " & Hex(SourceChecksum)
    Else
      EndMessage = EndMessage & vbCrLf & vbCrLf & "The source dungeon has a checksum value."
    End If
  Else
    EndMessage = EndMessage & vbCrLf & vbCrLf & "The source dungeon does not have a checksum value."
  End If
  'MsgBox Len(DestinationFileContent)

  ' Build destination file name with full path
  DestinationFileName = SourceFileName & ".CONVERTED"

  Err.Clear
  Set DestinationFile = FileSystemObject.OpenTextFile(DestinationFileName, ForWriting, True)
  If Err.Number <> 0 Then
    MsgBox "Unable to create the destination file: " & DestinationFileName & vbCrLf & Err.Description, vbOKOnly + vbCritical, "DungeonConverter Error"
  Else
    Err.Clear
    DestinationFile.Write(DestinationFileContent)
    If Err.Number <> 0 Then
      MsgBox "Unable to write the destination file." & vbCrLf & Err.Description, vbOKOnly + vbCritical, "DungeonConverter Error"
    Else
      MsgBox "The file " & vbCrLf & vbCrLf & DestinationFileName & vbCrLf & vbCrLf & " was successfully created." & vbCrLf & vbCrLf & EndMessage, vbOKOnly + vbInformation, "DungeonConverter Information"
    End If
    DestinationFile.Close
  End If
End Sub


Set ScriptArguments = WScript.Arguments
If ScriptArguments.Count = 0 Then
  MsgBox  "Converts Dungeon Master, Chaos Strikes Back and Dungeon Master II dungeon files between little endian and big endian formats." & vbCrLf & vbCrLf & _
    "Usage example: at a command prompt, type the following command line:" & vbCrLf & _
    "   DungeonConverter.vbs DUNGEON.DAT" & vbCrLf & _
    "You can also drag and drop a dungeon file on DungeonConverter.vbs in Windows Explorer." & vbCrLf & vbCrLf &  _
    "The converted file is saved in the same folder as the dungeon file." & vbCrLf & vbCrLf & _
    "Background information:" & vbCrLf & _
    "In dungeon files, some data is stored as single bytes, and some is stored as words (pairs of bytes). Words can be stored as little endian (least significant byte first, used in PC, Apple IIGS, PC-9801, FM-Towns, Super NES and IBM PS/V) or big endian (most significant byte first, used in Atari ST, Amiga, X68000, Macintosh and SegaCD)." & vbCrLf & vbCrLf & _
    "This script will automatically detect the format of the input file and convert it to the other format by swapping the bytes in each word of data.", vbOKOnly + vbInformation, "DungeonConverter Help"
Else
  SourceFileName = ScriptArguments(0)
  Set FileSystemObject = CreateObject("Scripting.FileSystemObject")
  If Not FileSystemObject.FileExists(SourceFileName) Then
    MsgBox "File not found: " & SourceFileName, vbOKOnly + vbCritical, "DungeonConverter Error"
  Else
    Set SourceFile = FileSystemObject.GetFile(SourceFileName)
    SourceFileSize = SourceFile.Size
    If SourceFileSize < 44 Then
      MsgBox "Incorrect dungeon file (File is too small for header)." & vbCrLf & Err.Description, vbOKOnly + vbCritical, "DungeonConverter Error"
    Else
      Err.Clear
      Set SourceFile = FileSystemObject.OpenTextFile(SourceFileName, ForReading)
      If Err.Number <> 0 Then
        MsgBox "Unable to open file: " & SourceFileName & vbCrLf & Err.Description, vbOKOnly + vbCritical, "DungeonConverter Error"
      Else
        EndianOK = True
        Endian = LITTLE_ENDIAN  ' Assume little endian
        Checksum = 0
        DestinationFileContent = ""
        ReadHeader
        If SourceFileSize < 44 + (NumberOfMaps * 16) Then
          EndianOK = False
        Else
          ReadMapDefinitions
          If (SourceFileSize = ComputeSize) Then
            blnChecksumInSource = False
          ElseIf (SourceFileSize = ComputeSize + 2) Then
            blnChecksumInSource = True
          Else
            EndianOK = False
          End If
        End If

        If EndianOK = False Then
          SourceFile.Close
          Set SourceFile = FileSystemObject.OpenTextFile(SourceFileName, ForReading)
          If Err.Number <> 0 Then
            MsgBox "Unable to open file: " & SourceFileName & vbCrLf & Err.Description, vbOKOnly + vbCritical, "DungeonConverter Error"
          Else
            EndianOK = True
            Endian = BIG_ENDIAN ' Assume little endian
            Checksum = 0
            DestinationFileContent = ""
            ReadHeader
            If SourceFileSize < 44 + (NumberOfMaps * 16) Then
              EndianOK = False
            Else
              ReadMapDefinitions
              If (SourceFileSize = ComputeSize) Then
                blnChecksumInSource = False
              ElseIf (SourceFileSize = ComputeSize + 2) Then
                blnChecksumInSource = True
              Else
                EndianOK = False
              End If
            End If
          End If
        End If

        If EndianOK = False Then
          MsgBox "Invalid dungeon file. The dungeon may be compressed.", vbOKOnly + vbCritical, "DungeonConverter Error"
        Else
          Dim EndMessage
          If Endian = LITTLE_ENDIAN Then
            EndMessage = "The input dungeon file is Little Endian."
          Else
            EndMessage = "The input dungeon file is Big Endian."
          End If
          EndMessage = EndMessage & vbCrLf & vbCrLf & "RandomGraphicsSeed " & RandomGraphicsSeed & vbCrLf & "MapDataSize " & MapDataSize & vbCrLf & "NumberOfMaps " & NumberOfMaps & vbCrLf & "TextDataSize " & TextDataSize & vbCrLf & "StartingPartyPosition " & StartingPartyPosition & vbCrLf & "ObjectListSize " & ObjectListSize & vbCrLf & "NumberOfDoors " & NumberOfDoors & vbCrLf & "NumberOfTeleporters " & NumberOfTeleporters & vbCrLf & "NumberOfTexts " & NumberOfTexts & vbCrLf & "NumberOfSensors " & NumberOfSensors & vbCrLf & "NumberOfGroups " & NumberOfGroups & vbCrLf & "NumberOfWeapons " & NumberOfWeapons & vbCrLf & "NumberOfArmours " & NumberOfArmours & vbCrLf & "NumberOfScrolls " & NumberOfScrolls & vbCrLf & "NumberOfPotions " & NumberOfPotions & vbCrLf & "NumberOfContainers " & NumberOfContainers & vbCrLf & "NumberOfJunks " & NumberOfJunks & vbCrLf & "NumberOfProjectiles " & NumberOfProjectiles & vbCrLf & "NumberOfExplosions " & NumberOfExplosions

          ConvertDungeon
        End If
        SourceFile.Close
      End If
    End If
  End If
End If