Author Topic: How to read the Summary Info, without AutoCAD  (Read 11173 times)

0 Members and 1 Guest are viewing this topic.

jgr

  • Guest
How to read the Summary Info, without AutoCAD
« on: December 27, 2010, 03:40:24 PM »
Example:
Code: [Select]
Public Shared Sub GetPropertiesTest(ByVal filename As String)

    Using fs As FileStream = New FileStream(filename, _
                                            FileMode.Open, _
                                            FileAccess.Read, _
                                            FileShare.ReadWrite)

        Using br As BinaryReader = New BinaryReader(fs)
            Dim versionId As String = br.ReadChars(6)
            Dim isUnicode As Boolean

            If versionId.Equals("AC1018") Then
                ' AutoCAD 2004
                isUnicode = False
            ElseIf versionId.Equals("AC1021") OrElse _
                    versionId.Equals("AC1024") Then
                ' AutoCAD 2007, 2010
                isUnicode = True
            Else
                ' 2000?->estoy trabajando en ello.
                Return
            End If

            '0x20->Summary info Address, 4 bytes
            fs.Seek(&H20, SeekOrigin.Begin)
            Dim smiAddress As UInteger = br.ReadUInt32
            fs.Seek(smiAddress, SeekOrigin.Begin)

            '-------------------------------------------------
            ' Predefined properties, in this order:
            ' title, subject, author, keywords, comments,
            ' lastSavedBy, revisionNumber??, hyperlinkBase

            For i As Integer = 0 To 7
                Dim tmp As String
                If isUnicode Then
                    tmp = ReadStringW(br)
                Else

                    tmp = ReadStringA(br)
                End If

                System.Diagnostics.Debug.Print(tmp)

            Next
            '-------------------------------------------------

            '-------------------------------------------------
            ' Total editing time, Create date time and Modified
            ' date time in the format:
            ' <Julian day number>.<Decimal fraction of a day>
            ' I'm not sure about this. Julian day is correct,
            ' but the fraction of a day...

            'Total editing time, 8 bytes
            Dim tet1 As UInteger = br.ReadUInt32 ' Julian day
            Dim tet2 As UInteger = br.ReadUInt32 '
            ' Create date time , 8 bytes
            Dim cdt1 As UInteger = br.ReadUInt32 ' Julian day
            Dim cdt2 As UInteger = br.ReadUInt32
            ' Modified date time, 8 bytes
            Dim mdt1 As UInteger = br.ReadUInt32 ' Julian day
            Dim mdt2 As UInteger = br.ReadUInt32
            '-------------------------------------------------

            '-------------------------------------------------
            ' Custom properties
            Dim pcount As Short = br.ReadInt16 ' Property count

            '<lenght><name string><lenght><value string>
            For i As Short = 0 To pcount - 1S
                Dim name As String
                Dim value As String

                If isUnicode Then
                    name = ReadStringW(br)
                    value = ReadStringW(br)
                Else
                    name = ReadStringA(br)
                    value = ReadStringA(br)
                End If

                System.Diagnostics.Debug.Print(name)
                System.Diagnostics.Debug.Print(value)
            Next
            '------------------------------------------------

        End Using
    End Using

End Sub

Auxiliar functions
Code: [Select]
Private Shared Function ReadStringW(ByVal br As BinaryReader) As String
    Dim len As Integer = br.ReadInt16

    If len > 0 Then
        Dim buf(len - 1) As Char
        For i As Integer = 0 To len - 1
            buf(i) = Convert.ToChar(br.ReadInt16)

        Next
        Return New String(buf).TrimEnd(Convert.ToChar(0))

    Else
        Return String.Empty
    End If
End Function

Private Shared Function ReadStringA(ByVal br As BinaryReader) As String
    Dim len As Integer = br.ReadInt16

    If len > 0 Then
        Dim tmp As String = Encoding.ASCII.GetString(br.ReadBytes(len))
        Return tmp.TrimEnd(Convert.ToChar(0))
    Else
        Return String.Empty
    End If

End Function

twdotson

  • Mosquito
  • Posts: 17
Re: How to read the Summary Info, without AutoCAD
« Reply #1 on: January 06, 2011, 05:05:42 PM »
I'm not sure about this. Julian day is correct, but the fraction of a day...

The second integers are "Milliseconds into the day".  I can post a code with TimeSpan/DateTime needed.
« Last Edit: January 07, 2011, 02:44:27 PM by User48 »

jgr

  • Guest
Re: How to read the Summary Info, without AutoCAD
« Reply #2 on: January 07, 2011, 05:32:41 AM »
The second integers are "Milliseconds into the day".  I can post a complete function with TimeSpan/DateTime (including added 2000 dwg support, which reads different) if anyone is interested.

I am (very) interested.

I know that is the second integer. But the value does not seem right, or maybe i do not know how to convert the date

To convert the date I have used this:
Code: [Select]
Private Shared Sub GetDate(ByVal JD As Double)
    Dim a As Integer
    Dim b As Integer
    Dim c As Integer
    Dim d As Integer
    Dim e As Integer
    Dim f As Integer

    Dim df As Double
    Dim z As Double

    z = CInt(Math.Truncate(JD))

    df = JD - z

    If z < 2299161 Then
        a = CInt(Math.Truncate(z))
    Else
        b = CInt(Math.Truncate((z - 1867216.25) / 36524.25))
        a = CInt(Math.Truncate(z + 1 + b - CInt(Math.Truncate(b \ 4))))
    End If
    c = a + 1524
    d = CInt(Math.Truncate((c - 122.1) / 365.25))
    e = CInt(Math.Truncate(365.25 * d))
    f = CInt(Math.Truncate((c - e) / 30.6001))


    Dim hour As Integer = CInt(Math.Truncate(df * 24))
    df -= CDbl(hour) / 24

    Dim minute As Integer = CInt(Math.Truncate(df * 1440))
    df -= CDbl(minute) / 1440

    Dim day As Integer = c - e - CInt(Math.Truncate(30.6001 * f))

    Dim month As Integer
    If f < 14 Then
        month = f - 1
    Else
        month = f - 13
    End If

    Dim year As Integer
    If month > 2 Then
        year = d - 4716
    Else
        year = d - 4715
    End If

End Sub

twdotson

  • Mosquito
  • Posts: 17
Re: How to read the Summary Info, without AutoCAD
« Reply #3 on: January 07, 2011, 01:03:15 PM »
This should work

Code: [Select]
Dim jub As New DateTime(1900, 1, 1, 19, 0, 0)
'Total editing time, 8 bytes
Dim tet1 As UInteger = br.ReadUInt32 ' Julian day
Dim tet2 As UInteger = br.ReadUInt32 '
Dim tims = TimeSpan.FromDays(tet1).Add(TimeSpan.FromMilliseconds(tet2))
System.Diagnostics.Debug.Print(Fix(tims.TotalHours).ToString & " Hours, " & tims.Minutes.ToString & " Minutes")
' Create date time , 8 bytes
Dim cdt1 As UInteger = br.ReadUInt32 ' Julian day
Dim cdt2 As UInteger = br.ReadUInt32
Dim cdto As DateTime = jub.AddDays(cdt1 - 2415022).AddMilliseconds(cdt2)
System.Diagnostics.Debug.Print(cdto.ToString)
' Modified date time, 8 bytes
Dim mdt1 As UInteger = br.ReadUInt32 ' Julian day
Dim mdt2 As UInteger = br.ReadUInt32
Dim mdto As DateTime = jub.AddDays(mdt1 - 2415022).AddMilliseconds(mdt2)
System.Diagnostics.Debug.Print(mdto.ToString)

jgr

  • Guest
Re: How to read the Summary Info, without AutoCAD
« Reply #4 on: January 07, 2011, 02:00:38 PM »
This should work
...

Thank you very much, I'll prove it.

Can you provide the code for acad 2000? .I'm gonna try with a hex editor, but I've never used that (and sorry, i do not speack english).


My code is based on this:

For the predefined properties:
Metadata extractor for DWG files
http://issues.alfresco.com/jira/browse/ALF-2262?page=com.atlassian.jira.plugin.system.issuetabpanels%3Aall-tabpanel

For other properties:
ODA Open Design Specification for .dwg files
http://opendesign.com/files/guestdownloads/OpenDesign_Specification_for_.dwg_files.pdf

Date conversion:
(I forgot where I found)
Code: [Select]
*! \fn void ln_get_date (double JD, struct ln_date * date)
* \param JD Julian day
* \param date Pointer to new calendar date.
*
* Calculate the date from the Julian day 
*/
void ln_get_date (double JD, struct ln_date * date)
{
   int A,a,B,C,D,E;
   double F,Z;
   
   JD += 0.5;
   Z = (int) JD;
   F = JD - Z;
   
   if (Z < 2299161)
       A = (int) Z;
   else {
       a = (int) ((Z - 1867216.25) / 36524.25);
       A = (int) (Z + 1 + a - (int)(a / 4));
   }
   
   B = A + 1524;
   C = (int) ((B - 122.1) / 365.25);
   D = (int) (365.25 * C);
   E = (int) ((B - D) / 30.6001);
   
   /* get the hms */
   date->hours = (int) (F * 24);
   F -= (double)date->hours / 24;
   date->minutes = (int) (F * 1440);
   F -= (double)date->minutes / 1440;
   date->seconds = F * 86400;
   
   /* get the day */
   date->days = B - D - (int)(30.6001 * E);
   
   /* get the month */
   if (E < 14)
       date->months = E - 1;
   else
       date->months = E - 13;
   
   /* get the year */
   if (date->months > 2)
       date->years = C - 4716;
   else
       date->years = C - 4715;
}
  else
       date->years = C - 4715;
}

kaefer

  • Guest
Re: How to read the Summary Info, without AutoCAD
« Reply #5 on: January 07, 2011, 06:11:47 PM »
Code: [Select]
Dim jub As New DateTime(1900, 1, 1, 19, 0, 0)
'Total editing time, 8 bytes
Dim tet1 As UInteger = br.ReadUInt32 ' Julian day
Dim tet2 As UInteger = br.ReadUInt32 '
Dim tims = TimeSpan.FromDays(tet1).Add(TimeSpan.FromMilliseconds(tet2))
System.Diagnostics.Debug.Print(Fix(tims.TotalHours).ToString & " Hours, " & tims.Minutes.ToString & " Minutes")
' Create date time , 8 bytes
Dim cdt1 As UInteger = br.ReadUInt32 ' Julian day
Dim cdt2 As UInteger = br.ReadUInt32
Dim cdto As DateTime = jub.AddDays(cdt1 - 2415022).AddMilliseconds(cdt2)
System.Diagnostics.Debug.Print(cdto.ToString)
' Modified date time, 8 bytes
Dim mdt1 As UInteger = br.ReadUInt32 ' Julian day
Dim mdt2 As UInteger = br.ReadUInt32
Dim mdto As DateTime = jub.AddDays(mdt1 - 2415022).AddMilliseconds(mdt2)
System.Diagnostics.Debug.Print(mdto.ToString)

It's very cool that you have a working solution. Let me guess that you are on the East Coast.

In your code there are a few implicit conversions to float; the reference date seems arbitrary too. That got me thinking how to build the TimeSpan and DateTime structs with the Ticks constructor. My suggestion in F# (prints UTC dates):

Code: [Select]
open System
open System.IO
open System.Text

module String =
    let ofAscii = Encoding.ASCII.GetString
    let ofUnicode = UnicodeEncoding.Unicode.GetString
module Date =
    let epoch = 1721426L // Number of days between JDN 0 and 1/1/1
    let ofJulian days milliseconds =
        new DateTime(
            (days - epoch) * TimeSpan.TicksPerDay +
            milliseconds * TimeSpan.TicksPerMillisecond )
    let ofUsrTime days milliseconds =
        new TimeSpan(
            days * TimeSpan.TicksPerDay +
            milliseconds * TimeSpan.TicksPerMillisecond )

let getPropertiesTest (filename: string) =
    use fs =
        new FileStream(
            filename,
            FileMode.Open,
            FileAccess.Read,
            FileShare.ReadWrite )
    use br = new BinaryReader(fs)
    try
        let decoding, lenChar =
            br.ReadBytes 6 |> String.ofAscii
            |> function
            | "AC1021" | "AC1024" -> String.ofUnicode, 2s
            | "AC1018" -> String.ofAscii, 1s
            | versionId ->
                new NotSupportedException(versionId) |> raise

        // 0x20->Summary info Address, 4 bytes
        fs.Seek(0x20L, SeekOrigin.Begin) |> ignore
        fs.Seek(br.ReadUInt32() |> int64, SeekOrigin.Begin) |> ignore
    
        // Predefined properties, in this order:
        // title, subject, author, keywords, comments,
        // lastSavedBy, revisionNumber??, hyperlinkBase
        for _ in 0 .. 7 do
            lenChar * br.ReadInt16() |> int
            |> br.ReadBytes |> decoding
            |> fun s -> s.TrimEnd '\000'
            |> Diagnostics.Debug.Print

        // Total editing time, 8 bytes
        let tdindwg =
            Date.ofUsrTime (br.ReadUInt32() |> int64) (br.ReadUInt32() |> int64)
        // Create date time , 8 bytes
        let tducreate =
            Date.ofJulian (br.ReadUInt32() |> int64) (br.ReadUInt32() |> int64)
        // Modified date time, 8 bytes
        let tduupdate =
            Date.ofJulian (br.ReadUInt32() |> int64) (br.ReadUInt32() |> int64)

        printfn
            "Created %s, Modified %s, EditTime %s"
            (string tducreate)
            (string tduupdate)
            (string tdindwg)
    finally
        br.Close()

Best Regards
« Last Edit: January 08, 2011, 12:28:19 AM by kaefer »

jgr

  • Guest
Re: How to read the Summary Info, without AutoCAD
« Reply #6 on: January 07, 2011, 06:47:12 PM »
...
It's very cool that you have a working solution. Let me guess that you are on the East Coast.
...
In your code there are a few implicit conversions to float; the reference date seems arbitrary too. That got me thinking how to build the TimeSpan and DateTime structs with the Ticks constructor. My suggestion in F# (prints UTC dates):
...

I do not understand.

That code (F#?) seems from another planet. http://en.wikipedia.org/wiki/Alien_%28film%29
I only "speak" visual basic, but with many problems, i can "read" C, C#, or Java.

In any case, thanks.





kaefer

  • Guest
Re: How to read the Summary Info, without AutoCAD
« Reply #7 on: January 08, 2011, 12:40:01 AM »

You were right, it didn't even compile (finally block missing). Edited.

Quote
i can "read" C, C#, or Java.

The correspondig C# for the date calculation doesn't look very different.
Code: [Select]
    static class Date
    {
        const long epoch = 1721426; // Number of days between JDN 0 and 1/1/1
        static DateTime ofJulian(long days, long milliseconds)
        {
            return new DateTime(
                (days - epoch) * TimeSpan.TicksPerDay +
                milliseconds * TimeSpan.TicksPerMillisecond);
        }
        static TimeSpan ofUsrTime(long days, long milliseconds)
        {
            return new TimeSpan(
                days * TimeSpan.TicksPerDay +
                milliseconds * TimeSpan.TicksPerMillisecond);
        }
    }

Cheers

jgr

  • Guest
Re: How to read the Summary Info, without AutoCAD
« Reply #8 on: January 11, 2011, 02:14:48 PM »
For AutoCad 2000 drawings

The easy way:
Code: [Select]
Imports System
Imports System.IO
Imports System.Text

Public NotInheritable Class SMI2000Easy

    Public Shared Sub read2000B(ByVal filename As String)

        Using fs As FileStream = New FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)

            Using br As binreader = New binreader(fs)

                Dim versionId As String = br.ReadChars(6)

                If Not versionId.Equals("AC1015", StringComparison.InvariantCultureIgnoreCase) Then
                    Return
                End If

                '  six bytes of 0 (in R14, 5 0’s and the ACADMAINTVER variable) and a byte of 1
                br.ReadBytes(7)

                ' Image seeker
                Dim imageSeeker As Integer = br.ReadInt32
                ' Unknown section
                br.ReadBytes(2)
                ' Dwgcodepage
                br.ReadBytes(2)

                Dim objectmapSeeker As Integer

                ' Section-locator records:
                Dim count As Integer = br.ReadInt32

                For i As Integer = 0 To count - 1

                    Dim recnum As Byte = br.ReadByte
                    Dim seeker As Integer = br.ReadInt32
                    Dim size As Integer = br.ReadInt32

                    Select Case recnum

                        Case 0

                            'System.Diagnostics.Debug.Print("Header variables")
                        Case 1

                            'System.Diagnostics.Debug.Print("Class section")
                        Case 2
                            'System.Diagnostics.Debug.Print("Object map")
                            objectmapSeeker = seeker

                        Case 3

                            'System.Diagnostics.Debug.Print("Unknown")
                        Case 4

                            'System.Diagnostics.Debug.Print("R14DATA")
                        Case 5

                            'System.Diagnostics.Debug.Print("R14REC5")
                        Case Else
                            Return
                    End Select

                Next

                fs.Seek(imageSeeker, SeekOrigin.Begin)

                Dim mask() As Byte = New Byte() {87, 71, 80, 82, 79, 80, 83, 32, 67, 79, 79, 75, 73, 69}

                While fs.Position < objectmapSeeker
                    ' Search DWGPROPS COOKIE

                    Dim d As Byte = br.ReadByte

                    If d <> 68 Then
                        Continue While
                    End If

                    Dim test() As Byte = br.ReadBytes(mask.Length)

                    For i As Integer = 0 To mask.Length - 1

                        If test(i) <> mask(i) Then
                            fs.Seek(-mask.Length, SeekOrigin.Current)
                            Continue While
                        End If

                    Next

                    '------------------------------------------------------------
                    ' Predefined properties, in this order:
                    ' title, subject, author, comments, keywords, 
                    ' lastSavedBy, revisionNumber
                    ' DXF codes : 2, 3, 4, 6, 7, 8, 9
                    For i As Integer = 0 To 6

                        Dim code As Short = br.ReadInt16    ' DXF code
                        Dim len As Short = br.ReadInt16     ' String lenght

                        br.ReadByte() ' ?

                        Dim bytes() As Byte = br.ReadBytes(len)
                        Dim value As String = Encoding.ASCII.GetString(bytes)

                        System.Diagnostics.Debug.Print(value)
                    Next

                    '------------------------------------------------------------

                    '------------------------------------------------------------
                    ' 10 custom properties in the format:
                    ' <name=value>
                    ' DXF codes : 300, 301, 302, 303, 304, 305, 306, 307, 308, 309
                    For i As Integer = 0 To 9

                        Dim code As Short = br.ReadInt16    ' DXF code
                        Dim len As Short = br.ReadInt16     ' String lenght

                        br.ReadByte() ' ?

                        Dim bytes() As Byte = br.ReadBytes(len)
                        Dim value As String = Encoding.ASCII.GetString(bytes)
                        'Dim pairs() As String = value.Split(New Char() {"="c})

                        System.Diagnostics.Debug.Print(value)
                    Next

                    ' Total editing time, Create date time and Modified
                    ' date time in the format:
                    ' <Julian day number>.<Decimal fraction of a day>
                    ' DXF codes: 40, 41 ,42
                    For i As Integer = 0 To 2

                        Dim code As Short = br.ReadInt16    ' DXF code
                        Dim value As Double = br.ReadDouble ' Value

                        System.Diagnostics.Debug.Print(value.ToString)
                    Next

                    '------------------------------------------------------------

                    '------------------------------------------------------------
                    ' hyperlinkBase
                    ' DXF code: 1
                    Dim c As Short = br.ReadInt16   ' DXF code
                    Dim l As Short = br.ReadInt16   ' String lenght

                    br.ReadByte() ' ? Unknow

                    Dim b() As Byte = br.ReadBytes(l)
                    Dim v As String = Encoding.ASCII.GetString(b)

                    System.Diagnostics.Debug.Print(v)
                    '------------------------------------------------------------

                    Return
                End While

            End Using
        End Using
    End Sub

End Class


The Hard way: Creating a small dwg reader
The code is not complete, still do not know how to decode an xrecord object. but if someone is interested:
Code: [Select]
Imports System
Imports System.Text
Imports System.IO
Imports System.Collections
Imports System.Globalization

Public NotInheritable Class SMI2000Hard

    Public Shared Sub read2000(ByVal filename As String)

        Using fs As FileStream = New FileStream(filename, _
                                                FileMode.Open, _
                                                FileAccess.Read, _
                                                FileShare.ReadWrite)

            Using br As binreader = New binreader(fs)

                Dim versionId As String = br.ReadChars(6)

                If Not versionId.Equals("AC1015", StringComparison.InvariantCultureIgnoreCase) Then
                    Return
                End If

                '  six bytes of 0 (in R14, 5 0’s and the ACADMAINTVER variable) and a byte of 1
                br.ReadBytes(7)

                ' Image seeker
                Dim imageSeeker As Integer = br.ReadInt32
                ' Unknown section
                br.ReadBytes(2)
                ' Dwgcodepage
                br.ReadBytes(2)

                Dim objectmapSeeker As Integer

                ' Section-locator records:
                Dim count As Integer = br.ReadInt32
                For i As Integer = 0 To count - 1
                    Dim recnum As Byte = br.ReadByte
                    Dim seeker As Integer = br.ReadInt32
                    Dim size As Integer = br.ReadInt32

                    Select Case recnum
                        Case 0
                            'System.Diagnostics.Debug.Print("Header variables")
                        Case 1
                            'System.Diagnostics.Debug.Print("Class section")
                        Case 2
                            'System.Diagnostics.Debug.Print("Object map")
                            objectmapSeeker = seeker
                        Case 3
                            'System.Diagnostics.Debug.Print("Unknown")
                        Case 4
                            'System.Diagnostics.Debug.Print("R14DATA")
                        Case 5
                            'System.Diagnostics.Debug.Print("R14REC5")
                        Case Else
                            Return
                    End Select
                Next

                fs.Seek(objectmapSeeker, SeekOrigin.Begin)

                Dim loc As Long
                Do
                    '-------------------------------------------------
                    ' BIG ENDIAN
                    Dim b() As Byte = br.ReadBytes(2)
                    Array.Reverse(b)
                    Dim size As Short = BitConverter.ToInt16(b, 0)
                    '-------------------------------------------------

                    If size = 2 Then
                        Exit Do
                    End If

                    Dim dataBytes(size - 1) As Byte

                    For i As Integer = 0 To dataBytes.Length - 1
                        dataBytes(i) = br.ReadByte
                    Next i

                    Dim lastHandle As Integer = 0
                    Dim lastLoc As Integer = 0
                    Dim bitPos As Integer = 0
                    Dim bitMax As Integer = (size - 2) * 8

                    loc = fs.Position

                    Do While bitPos < bitMax

                        Dim v As ArrayList = GetModularChar(dataBytes, bitPos)
                        bitPos = CInt(v(0))
                        lastHandle = lastHandle + CInt(v(1))

                        v = GetModularChar(dataBytes, bitPos)
                        bitPos = (CInt(v(0)))
                        lastLoc = lastLoc + CInt(v(1))

                        ' Search for XRecord
                        'If TestDwgObject(br, lastLoc) Then
                        '    Return
                        'End If

                        TestDwgObject(br, lastLoc)

                    Loop

                    fs.Seek(loc, SeekOrigin.Begin)
                Loop


            End Using
        End Using
    End Sub

    Private Shared Function TestDwgObject(ByVal br As binreader, _
                                        ByVal offset As Integer) As Boolean
        br.BaseStream.Seek(offset, SeekOrigin.Begin)

        Dim size As Integer = GetModularShort(br)
        Dim dataBytes(size - 1) As Byte
        Dim data(size - 1) As Integer

        For i As Integer = 0 To size - 1
            dataBytes(i) = br.ReadByte
        Next

        Dim bitPos As Integer = 0
        Dim v As ArrayList = GetBitShort(dataBytes, bitPos)
        bitPos = CInt(v(0))

        ' Object Type
        Dim type As Integer = CInt(v(1))

        ' If type = &H2A  then
        ' object = DICCTIONAY

        ' If type <> XRecord
        If type <> &H4F Then
            Return Nothing
        End If

        ' Object Size
        v = GetRawLong(dataBytes, bitPos)
        bitPos = CInt(v(0))

        ' Handle (¿?no parece correcto)
        Dim entityHandle As New ArrayList()
        v = GetHandle(dataBytes, bitPos)
        bitPos = CInt(v(0))

        ' Extended data
        v = ReadExtendedData(dataBytes, bitPos)
        bitPos = CInt(v(0))

        ' Number of persistent reactors
        v = GetBitLong(dataBytes, bitPos)
        bitPos = CInt(v(0))

        Dim nr As Integer = CInt(v(1))

        ' Number of databytes
        v = GetBitLong(dataBytes, bitPos)
        bitPos = CInt(v(0))

        ' OOOOPS
        ' AND NOW????????????????????????????

    End Function
   
'.......
'...More functions
'See SMI2000Hard.vb

End Class