' ***********************************************************************
' Author : Elektro.
' Based on this 3rd party project:
' https://github.com/Zeokat/SNES-ROM-Header-Dumper-CSharp/blob/master/snes_dumper.cs
' Modified : 10-June-2015
' ***********************************************************************
' <copyright file="SnesRom.vb" company="Elektro Studios">
' Copyright (c) Elektro Studios. All rights reserved.
' </copyright>
' ***********************************************************************
#Region " Usage Examples "
' CONTENIDO OMITIDO...
#End Region
#Region " Option Statements "
Option Strict On
Option Explicit On
Option Infer Off
#End Region
#Region " Imports "
Imports System.IO
Imports System.Text
#End Region
Public NotInheritable Class SnesRom
#Region " Properties "
''' <summary>
''' Gets the raw byte-data of the ROM file.
''' </summary>
''' <value>The raw byte-data of the ROM file.</value>
Public ReadOnly Property RawData As Byte()
Get
Return Me.rawDataB
End Get
End Property
''' <summary>
''' (backing field) The raw byte-data of the ROM file.
''' </summary>
Private ReadOnly rawDataB As Byte()
''' <summary>
''' Gets The ROM header type.
''' </summary>
''' <remarks>http://romhack.wikia.com/wiki/SMC_header</remarks>
''' <value>The ROM header type.</value>
Public ReadOnly Property HeaderType As HeaderTypeEnum
Get
Return Me.headerTypeB
End Get
End Property
''' <summary>
''' (backing field) The ROM header type.
''' </summary>
Private headerTypeB As HeaderTypeEnum
''' <summary>
''' Gets the SNES header address location.
''' </summary>
''' <remarks>http://romhack.wikia.com/wiki/SNES_header</remarks>
''' <value>The SNES header address location.</value>
Private ReadOnly Property HeaderLocation As Integer
Get
Return Me.headerLocationB
End Get
End Property
''' <summary>
''' (backing field) The SNES header address location.
''' </summary>
Private headerLocationB As Integer = 33216
''' <summary>
''' Gets or sets the name of the ROM, typically in ASCII.
''' The name buffer consists in 21 characters.
''' </summary>
''' <remarks>http://romhack.wikia.com/wiki/SNES_header</remarks>
''' <value>The name of the ROM.</value>
Public Property Name As String
Get
Return Me.nameB
End Get
Set(ByVal value As String)
Me.SetName(value)
Me.nameB = value
End Set
End Property
''' <summary>
''' (backing field) The name of the ROM.
''' </summary>
Private nameB As String
''' <summary>
''' Gets the ROM layout.
''' The SNES ROM layout describes how the ROM banks appear in a ROM image and in the SNES address space.
''' </summary>
''' <remarks>http://romhack.wikia.com/wiki/SNES_ROM_layout</remarks>
''' <value>The ROM layout.</value>
Public ReadOnly Property Layout As Byte
Get
Return Me.layoutB
End Get
End Property
''' <summary>
''' (backing field) The ROM layout.
''' </summary>
Private layoutB As Byte
''' <summary>
''' Gets the bank type.
''' An image contains only LoROM banks or only HiROM banks, not both.
''' </summary>
''' <remarks>http://romhack.wikia.com/wiki/SNES_ROM_layout</remarks>
''' <value>The bank type.</value>
Public ReadOnly Property BankType As BankTypeEnum
Get
Return Me.bankTypeB
End Get
End Property
''' <summary>
''' (backing field) The bank type.
''' </summary>
Private bankTypeB As BankTypeEnum
''' <summary>
''' Gets the cartrifge type, it can be a ROM only, or a ROM with save-RAM.
''' </summary>
''' <remarks>http://romhack.wikia.com/wiki/SNES_header</remarks>
''' <value>The cartridge type.</value>
Public ReadOnly Property CartridgeType As CartridgeTypeEnum
Get
Return Me.cartridgeTypeB
End Get
End Property
''' <summary>
''' (backing field) The cartrifge type.
''' </summary>
Private cartridgeTypeB As CartridgeTypeEnum
''' <summary>
''' Gets the ROM size byte.
''' </summary>
''' <remarks>http://romhack.wikia.com/wiki/SNES_header</remarks>
''' <value>The ROM size byte.</value>
Public ReadOnly Property RomSize As Byte
Get
Return Me.romSizeB
End Get
End Property
''' <summary>
''' (backing field) The ROM size byte.
''' </summary>
Private romSizeB As Byte
''' <summary>
''' Gets the RAM size byte.
''' </summary>
''' <remarks>http://romhack.wikia.com/wiki/SNES_header</remarks>
''' <value>The RAM size byte.</value>
Public ReadOnly Property RamSize As Byte
Get
Return Me.ramSizeB
End Get
End Property
''' <summary>
''' (backing field) The ROM size byte.
''' </summary>
Private ramSizeB As Byte
''' <summary>
''' Gets or sets the country data.
''' </summary>
''' <remarks>http://romhack.wikia.com/wiki/SNES_header</remarks>
''' <value>The country data.</value>
Public Property Country As CountryData
Get
Return New CountryData(Me.CountryCode)
End Get
Set(ByVal value As CountryData)
Me.SetByte(Me.startAddressCountryCode, value.Code)
Me.CountryCode = value.Code
End Set
End Property
''' <summary>
''' The country code.
''' </summary>
Private Property CountryCode As Byte
''' <summary>
''' Gets or sets the license code.
''' </summary>
''' <remarks>http://romhack.wikia.com/wiki/SNES_header</remarks>
''' <value>The license code.</value>
Public Property LicenseCode As Byte
Get
Return Me.licenseCodeB
End Get
Set(ByVal value As Byte)
Me.SetByte(Me.startAddressLicenseCode, value)
Me.licenseCodeB = value
End Set
End Property
''' <summary>
''' (backing field) The license code.
''' </summary>
Private licenseCodeB As Byte
''' <summary>
''' Gets or sets the version number.
''' </summary>
''' <remarks>http://romhack.wikia.com/wiki/SNES_header</remarks>
''' <value>The version number.</value>
Public Property VersionNumber As Byte
Get
Return Me.versionNumberB
End Get
Set(ByVal value As Byte)
Me.SetByte(Me.startAddressVersionNumber, value)
Me.versionNumberB = value
End Set
End Property
''' <summary>
''' (backing field) The version number.
''' </summary>
Private versionNumberB As Byte
''' <summary>
''' Gets the checksum compliment.
''' </summary>
''' <remarks>http://romhack.wikia.com/wiki/SNES_header</remarks>
''' <value>The checksum compliment.</value>
Public ReadOnly Property ChecksumCompliment As UShort
Get
Return Me.checksumComplimentB
End Get
End Property
''' <summary>
''' (backing field) The checksum compliment.
''' </summary>
Private checksumComplimentB As UShort
''' <summary>
''' Gets the checksum.
''' </summary>
''' <remarks>http://romhack.wikia.com/wiki/SNES_header</remarks>
''' <value>The checksum.</value>
Public ReadOnly Property Checksum As UShort
Get
Return Me.checksumB
End Get
End Property
''' <summary>
''' (backing field) The checksum.
''' </summary>
Private checksumB As UShort
#End Region
#Region " Header Addresses "
' ********************************************************************************************************************
' NOTE:
' The reason for the variables that are commented-out is just because are unused, but could be helpful in the future.
' ********************************************************************************************************************
' ''' <summary>
' ''' The start address of a Lo-ROM header.
' ''' </summary>
'Private ReadOnly loRomHeaderAddress As UShort = 32704
' ''' <summary>
' ''' The start address of a Hi-ROM header.
' ''' </summary>
'Private ReadOnly hiRomHeaderAddress As UShort = 65472
''' <summary>
''' The start address of the ROM name.
''' </summary>
Private ReadOnly startAddressName As Integer = 0
''' <summary>
''' The end address of the ROM name.
''' </summary>
Private ReadOnly endAddressName As Integer = 20
''' <summary>
''' The start address of the ROM layout.
''' </summary>
Private ReadOnly startAddressLayout As Integer = 21
' ''' <summary>
' ''' The end address of the ROM layout.
' ''' </summary>
'Private ReadOnly endAddressLayout As Integer = 21
''' <summary>
''' The start address of the ROM cartridge type.
''' </summary>
Private ReadOnly startAddressCartridgeType As Integer = 22
' ''' <summary>
' ''' The end address of the ROM cartridge type.
' ''' </summary>
'Private ReadOnly endAddressCartridgeType As Integer = 22
''' <summary>
''' The start address of the ROM size (rom).
''' </summary>
Private ReadOnly startAddressRomSize As Integer = 23
' ''' <summary>
' ''' The end address of the ROM size (rom).
' ''' </summary>
'Private ReadOnly endAddressRomSize As Integer = 23
''' <summary>
''' The start address of the ROM size (ram).
''' </summary>
Private ReadOnly startAddressRamSize As Integer = 24
' ''' <summary>
' ''' The end address of the ROM size (ram).
' ''' </summary>
'Private ReadOnly endAddressRamSize As Integer = 24
''' <summary>
''' The start address of the ROM country code.
''' </summary>
Private ReadOnly startAddressCountryCode As Integer = 25
' ''' <summary>
' ''' The end address of the ROM country code.
' ''' </summary>
'Private ReadOnly endAddressCountryCode As Integer = 25
''' <summary>
''' The start address of the ROM license code.
''' </summary>
Private ReadOnly startAddressLicenseCode As Integer = 26
' ''' <summary>
' ''' The end address of the ROM license code.
' ''' </summary>
'Private ReadOnly endAddressLicenseCode As Integer = 26
''' <summary>
''' The start address of the ROM Version Number.
''' </summary>
Private ReadOnly startAddressVersionNumber As Integer = 27
' ''' <summary>
' ''' The end address of the ROM Version Number.
' ''' </summary>
'Private ReadOnly endAddresVersionNumber As Integer = 27
''' <summary>
''' The start address of the ROM checksum compliment.
''' </summary>
Private ReadOnly startAddressChecksumCompliment As Integer = 28
''' <summary>
''' The end address of the ROM checksum compliment.
''' </summary>
Private ReadOnly endAddressChecksumCompliment As Integer = 29
''' <summary>
''' The start address of the ROM checksum.
''' </summary>
Private ReadOnly startAddressChecksum As Integer = 30
''' <summary>
''' The end address of the ROM checksum.
''' </summary>
Private ReadOnly endAddressChecksum As Integer = 31
#End Region
#Region " Enumerations "
''' <summary>
''' Specifies a SNES ROM header type.
''' A headered ROM has SMC header and SNES header.
''' A headerless ROM has no SMC header, but still contains a SNES header.
''' Note that both a LoRom and HiRom images can be headered, or headerless.
''' <remarks>http://romhack.wikia.com/wiki/SNES_header</remarks>
''' </summary>
Public Enum HeaderTypeEnum As Integer
''' <summary>
''' A headered SNES ROM.
''' The ROM contains an SMC header, and also contains an SNES header.
''' </summary>
Headered = 0
''' <summary>
''' A headerless SNES ROM.
''' The ROM does not contains an SMC header, but contains an SNES header.
''' </summary>
Headerless = 1
End Enum
''' <summary>
''' Specifies a SNES ROM bank type.
''' <remarks>http://romhack.wikia.com/wiki/SNES_ROM_layout</remarks>
''' </summary>
Public Enum BankTypeEnum As UShort
''' <summary>
''' A LoROM maps each ROM bank into the upper half (being addresses $8000 to $ffff) of each SNES bank,
''' starting with SNES bank $00, and starting again with SNES bank $80.
''' </summary>
LoRom = 32704US
''' <summary>
''' A HiROM maps each ROM bank into the whole (being addresses $0000 to $ffff) of each SNES bank,
''' starting with SNES bank $40, and starting again with SNES bank $80.
''' </summary>
HiRom = 65472US
End Enum
''' <summary>
''' Specifies a SNES ROM cartridge type.
''' <remarks>http://romhack.wikia.com/wiki/SNES_ROM_layout</remarks>
''' </summary>
Public Enum CartridgeTypeEnum As Byte
''' <summary>
''' A ROM without save-RAM.
''' </summary>
NoSram0 = 0
''' <summary>
''' A ROM without save-RAM.
''' <remarks>I didn't fully verified this value...</remarks>
''' </summary>
NoSram1 = 1
''' <summary>
''' A ROM with save-RAM.
''' </summary>
Sram = 2
End Enum
#End Region
#Region " Exceptions "
''' <summary>
''' Exception that is thrown when a SNES ROM has an invalid format.
''' </summary>
<Serializable>
Public NotInheritable Class InvalidRomFormatException : Inherits Exception
''' <summary>
''' Initializes a new instance of the <see cref="InvalidROMFormatException"/> class.
''' </summary>
Public Sub New()
MyBase.New("The SNES ROM image has an invalid format.")
End Sub
''' <summary>
''' Initializes a new instance of the <see cref="InvalidROMFormatException"/> class.
''' </summary>
''' <param name="message">The message that describes the error.</param>
Public Sub New(ByVal message As String)
MyBase.New(message)
End Sub
''' <summary>
''' Initializes a new instance of the <see cref="InvalidROMFormatException"/> class.
''' </summary>
''' <param name="message">The message that describes the error.</param>
''' <param name="inner">The inner exception.</param>
Public Sub New(ByVal message As String, ByVal inner As Exception)
MyBase.New(message, inner)
End Sub
End Class
#End Region
#Region " Types "
''' <summary>
''' Defines a SNES ROM country.
''' </summary>
<Serializable>
Public NotInheritable Class CountryData
#Region " Properties "
''' <summary>
''' Gets the region, which can de PAL or NTSC.
''' </summary>
''' <remarks>http://romhack.wikia.com/wiki/SNES_header</remarks>
''' <value>The country code.</value>
Public ReadOnly Property Region As RegionTypeEnum
Get
Return Me.regionB
End Get
End Property
''' <summary>
''' (backing field) The region, which can de PAL or NTSC.
''' </summary>
Private ReadOnly regionB As RegionTypeEnum
''' <summary>
''' Gets the country code.
''' </summary>
''' <value>The country code.</value>
Public ReadOnly Property Code As Byte
Get
Return Me.codeB
End Get
End Property
''' <summary>
''' (backing field) The country code.
''' </summary>
Private ReadOnly codeB As Byte
''' <summary>
''' Gets the country name.
''' </summary>
''' <value>The country name.</value>
Public ReadOnly Property Name As String
Get
Return Me.nameB
End Get
End Property
''' <summary>
''' (backing field) The country name.
''' </summary>
Private ReadOnly nameB As String
#End Region
#Region " Enumerations "
''' <summary>
''' Specifies a SNES ROM region type.
''' <remarks>http://romhack.wikia.com/wiki/SNES_header</remarks>
''' </summary>
Public Enum RegionTypeEnum As Integer
''' <summary>
''' A PAL SNES ROM.
''' </summary>
Pal = 0
''' <summary>
''' An NTSC SNES ROM.
''' </summary>
Ntsc = 1
End Enum
#End Region
#Region " Countries "
''' <summary>
''' The known ROM countries, based on country code from 0 to 13, so countrycode 0 = Japan, countrycode 1 = United States, and so on...
''' Unknown country codes are just unknown.
''' </summary>
Private ReadOnly countryDict
As New Dictionary(Of Integer,
String) From
{
{0, "Japan"},
{1, "United States"},
{2, "Unknown"},
{3, "Unknown"},
{4, "Unknown"},
{5, "Unknown"},
{6, "Unknown"},
{7, "Unknown"},
{8, "Spain"},
{9, "Unknown"},
{10, "Unknown"},
{11, "Unknown"},
{12, "Unknown"},
{13, "Unknown"}
}
#End Region
#Region " Regions "
''' <summary>
''' The country codes for NTSC region.
''' <remarks>http://romhack.wikia.com/wiki/SMC_header</remarks>
''' </summary>
Private ReadOnly ntscRegionCodes As Integer() =
{0, 1, 13}
''' <summary>
''' The country codes for PAL region.
''' <remarks>http://romhack.wikia.com/wiki/SMC_header</remarks>
''' </summary>
Private ReadOnly palRegionCodes As Integer() =
{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
#End Region
#Region " Constructors "
''' <summary>
''' Initializes a new instance of the <see cref="CountryData"/> class.
''' </summary>
''' <param name="countryCode">The SNES ROM country code.</param>
''' <exception cref="ArgumentException">Invalid country code.;countryCode</exception>
Public Sub New(ByVal countryCode As Byte)
If Not (Me.ntscRegionCodes.Concat(Me.palRegionCodes)).Contains(countryCode) Then
Throw New ArgumentException(message:="Invalid country code.", paramName:="countryCode")
Else
Me.codeB = countryCode
Me.nameB = Me.countryDict(countryCode)
' Determine region.
If Me.ntscRegionCodes.Contains(countryCode) Then
Me.regionB = RegionTypeEnum.Ntsc
ElseIf Me.palRegionCodes.Contains(countryCode) Then
Me.regionB = RegionTypeEnum.Pal
End If
End If
End Sub
''' <summary>
''' Prevents a default instance of the <see cref="CountryData"/> class from being created.
''' </summary>
Private Sub New()
End Sub
#End Region
End Class
#End Region
#Region " Constructors "
''' <summary>
''' Prevents a default instance of the <see cref="SnesRom"/> class from being created.
''' </summary>
Private Sub New()
End Sub
''' <summary>
''' Initializes a new instance of the <see cref="SnesRom"/> class.
''' </summary>
''' <param name="romFilePath">The SNES ROM file path.</param>
Public Sub New(ByVal romFilePath As String)
Me.
New(File.
ReadAllBytes(romFilePath
))
End Sub
''' <summary>
''' Initializes a new instance of the <see cref="SnesRom"/> class.
''' </summary>
''' <param name="romData">The raw byte-data of the ROM file.</param>
Public Sub New(ByVal romData As Byte())
Me.rawDataB = romData
Me.VerifyRomFormat()
Me.VerifyBankType()
Me.ReadHeader()
End Sub
#End Region
#Region " Private Methods "
''' <summary>
''' Reads the ROM header to retrieve the header data.
''' </summary>
Private Sub ReadHeader()
' Read range of bytes.
Me.nameB = Encoding.ASCII.GetString(Me.GetBytes(Me.startAddressName, Me.endAddressName)).Trim
' Read single bytes.
Me.layoutB = Me.GetByte(Me.startAddressLayout)
Me.cartridgeTypeB = DirectCast(Me.GetByte(Me.startAddressCartridgeType), CartridgeTypeEnum)
Me.romSizeB = Me.GetByte(Me.startAddressRomSize)
Me.ramSizeB = Me.GetByte(Me.startAddressRamSize)
Me.CountryCode = Me.GetByte(Me.startAddressCountryCode)
Me.LicenseCode = Me.GetByte(Me.startAddressLicenseCode)
Me.VersionNumber = Me.GetByte(Me.startAddressVersionNumber)
End Sub
''' <summary>
''' Verifies the SNES ROM format.
''' </summary>
''' <exception cref="SnesRom.InvalidRomFormatException">The SNES ROM image has an invalid format.</exception>
Private Sub VerifyRomFormat()
If (Me.rawDataB.Length Mod 1024 = 512) Then
Me.headerTypeB = HeaderTypeEnum.Headered
ElseIf (Me.rawDataB.Length Mod 1024 = 0) Then
Me.headerTypeB = HeaderTypeEnum.Headerless
Else
Throw New InvalidRomFormatException(message:="The SNES ROM image has an invalid format.")
End If
End Sub
''' <summary>
''' Verifies the SNES ROM bank type.
''' </summary>
''' <exception cref="Exception">Cannot recognize the bank type.</exception>
Private Sub VerifyBankType()
If Me.HeaderIsAt(BankTypeEnum.LoRom) Then
Me.bankTypeB = BankTypeEnum.LoRom
ElseIf Me.HeaderIsAt(BankTypeEnum.HiRom) Then
Me.bankTypeB = BankTypeEnum.HiRom
Else
Throw New Exception(message:="Cannot recognize the bank type.")
End If
End Sub
''' <summary>
''' Verifies the checksum.
''' </summary>
''' <remarks>
''' Offset 0x07FC0 in a headerless LoROM image (LoROM rom sin smc header)
''' Offset 0x0FFC0 in a headerless HiROM image (HiROM rom sin smc header)
''' </remarks>
''' <returns><c>true</c> if checksum is ok, <c>false</c> otherwise.</returns>
Private Function VerifyChecksum() As Boolean
If Me.HeaderType = HeaderTypeEnum.Headered Then
Me.headerLocationB += 512
End If
Me.checksumComplimentB = BitConverter.ToUInt16(Me.GetBytes(Me.startAddressChecksumCompliment, Me.endAddressChecksumCompliment), startIndex:=0)
Me.checksumB = BitConverter.ToUInt16(Me.GetBytes(Me.startAddressChecksum, Me.endAddressChecksum), startIndex:=0)
Return CUShort(Me.Checksum Xor Me.ChecksumCompliment).Equals(UShort.MaxValue)
End Function
''' <summary>
''' Determines whether the ROM header is in the specified address.
''' </summary>
''' <param name="address">The address.</param>
''' <returns><c>true</c> if the ROM header is in the specified address, <c>false</c> otherwise.</returns>
Private Function HeaderIsAt(ByVal address As UShort) As Boolean
Me.headerLocationB = address
Return Me.VerifyChecksum()
End Function
''' <summary>
''' Gets the specified byte from the raw byte-data.
''' </summary>
''' <param name="address">The address.</param>
''' <returns>The specified byte from the raw byte-data.</returns>
Private Function GetByte(ByVal address As Integer) As Byte
Return Buffer.GetByte(array:=Me.RawData,
index:=Me.HeaderLocation + address)
End Function
''' <summary>
''' Gets the specified range of bytes from the raw byte-data.
''' </summary>
''' <param name="from">From address.</param>
''' <param name="to">To address.</param>
''' <returns>The specified bytes from the raw byte-data.</returns>
Private Function GetBytes(ByVal from As Integer,
ByVal [to] As Integer) As Byte()
Return Me.RawData.Skip(Me.HeaderLocation + from).Take(([to] - from) + 1).ToArray()
End Function
''' <summary>
''' Replaces a single byte in the raw byte-data, with the specified data.
''' </summary>
''' <param name="address">the address.</param>
''' <param name="data">The byte-data.</param>
Private Sub SetByte(ByVal address As Integer,
ByVal data As Byte)
Buffer.SetByte(array:=Me.rawDataB,
index:=Me.HeaderLocation + address,
value:=data)
End Sub
''' <summary>
''' Replaces the specified range of bytes in the raw byte-data, with the specified data.
''' </summary>
''' <param name="from">From address.</param>
''' <param name="to">To address.</param>
''' <param name="data">The byte-data.</param>
''' <exception cref="ArgumentException">The byte-length of the specified data differs from the byte-length to be replaced;data</exception>
Private Sub SetBytes(ByVal from As Integer,
ByVal [to] As Integer,
ByVal data As Byte())
If data.Length <> (([to] - from) + 1) Then
Throw New ArgumentException("The byte-length of the specified data differs from the byte-length to be replaced.", "data")
Else
Buffer.BlockCopy(src:=data, srcOffset:=0,
dst:=Me.rawDataB, dstOffset:=Me.HeaderLocation + from,
count:=([to] - from) + 1)
End If
End Sub
''' <summary>
''' Sets the ROM name.
''' </summary>
''' <param name="name">The ROM name.</param>
''' <exception cref="ArgumentNullException">name</exception>
''' <exception cref="ArgumentException">The name should contain 21 or less characters;name.</exception>
Private Sub SetName(ByVal name As String)
Dim fixedNameLength As Integer = (Me.endAddressName - Me.startAddressName) + 1
If String.IsNullOrEmpty(name) Then
Throw New ArgumentNullException(paramName:="name")
ElseIf (name.Length > fixedNameLength) Then
Throw New ArgumentException(message:="The name should contain 21 or less characters.", paramName:="name")
Else
' fill with spaces up to 21 character length.
name = name.PadRight(totalWidth:=fixedNameLength, paddingChar:=" "c)
Me.SetBytes(Me.startAddressName, Me.endAddressName, Encoding.ASCII.GetBytes(name))
End If
End Sub
#End Region
#Region " Public Methods "
''' <summary>
''' Save the ROM changes to the specified file path.
''' </summary>
''' <param name="filePath">The ROM file path.</param>
''' <param name="replace">
''' If set to <c>true</c>, then replaces any existing file,
''' otherwise, throws an <see cref="IOException"/> exception if file already exists.
''' </param>
''' <exception cref="IOException">The destination file already exists.</exception>
Public Sub Save(ByVal filePath As String, ByVal replace As Boolean)
If Not replace AndAlso File.
Exists(filePath
) Then Throw New IOException(message:="The destination file already exists.")
Else
Try
File.
WriteAllBytes(filePath,
Me.
rawDataB)
Catch ex As Exception
Throw
End Try
End If
End Sub
#End Region
End Class