Microsoft.VisualBasic.VBMath.Rnd()

I have a game I am converting to Unity script, it uses Microsoft.VisualBasic.VBMath.Rnd() the random number generator from Visual Basic.

I need to get the same exact sequence of random numbers every time when using the same seed. I have a lot of test scripts for my game that depend on this.

Is there a way to get it this function from Mono (it is in regular Mono) into my build and have it work on all platforms?

I have my project set for full instead of subset Mono but I am not able to resolve VBMath without using Microsoft.VisualBasic.Compatibility.VB6;

When I edit the project references in the MonoDevelop IDE and add the package Microsoft.VisualBasic.Compatibility.VB6 it will build in the IDE. But when I run the project from Unity the console says the namespace Compatibility doesn’t exist.

I have tried also to find the source code for the function or a C# exact version of it with no luck.

  1. Why does using Microsoft.VisualBasic; work but not provide the VBMath type with the function?
  2. If I can’t include in my build does anyone know where to find the source for this function?

Thanks all for any help you can provide!
Scott Adams
(Not Dilbert, Adventure!)

or there’s system random

1 Like

Try searching your computer for Microsoft.VisualBasic.dll (I found different versions on my computer in Windows/Microsoft.NET), and dump a copy of that file in your project’s Assets/Plugins folder. Unity knows to grab the standard .NET DLLs, but non-standard assemblies need to be very explicitly included into the project; I believe that merely adding a reference in the MonoDevelop/Visual Studio project isn’t enough.

1 Like

Will that work for other non-intel platforms (IOS, Droid etc)? Isn’t a DLL in native assembler and not in CLR?

I suspect these are not the same sequence that VB produces for its random numbers. I need it to be identical. I suppose I can run some tests and check.

Decompiling System.VisualBasic.dll version 8.0 (on my computer I have version 8 and 10, probably doesn’t change much from version to version), this is what we can find for the code:

using Microsoft.VisualBasic.CompilerServices;
using System;

namespace Microsoft.VisualBasic
{
  /// <summary>
  /// The VbMath module contains procedures used to perform mathematical operations.
  /// </summary>
  /// <filterpriority>1</filterpriority>
  [StandardModule]
  public sealed class VBMath
  {
    /// <summary>
    /// Returns a random number of type Single.
    /// </summary>
    ///
    /// <returns>
    /// The next random number in the sequence.
    /// </returns>
    /// <filterpriority>1</filterpriority>
    public static float Rnd()
    {
      return VBMath.Rnd(1f);
    }

    /// <summary>
    /// Returns a random number of type Single.
    /// </summary>
    ///
    /// <returns>
    /// If number is less than zero, Rnd generates the same number every time, using <paramref name="Number"/> as the seed. If number is greater than zero, Rnd generates the next random number in the sequence. If number is equal to zero, Rnd generates the most recently generated number. If number is not supplied, Rnd generates the next random number in the sequence.
    /// </returns>
    /// <param name="Number">Optional. A Single value or any valid Single expression.</param><filterpriority>1</filterpriority>
    public static float Rnd(float Number)
    {
      ProjectData projectData = ProjectData.GetProjectData();
      int num1 = projectData.m_rndSeed;
      if ((double) Number != 0.0)
      {
        if ((double) Number < 0.0)
        {
          long num2 = (long) BitConverter.ToInt32(BitConverter.GetBytes(Number), 0) & (long) uint.MaxValue;
          num1 = checked ((int) (num2 + (num2 >> 24) & 16777215L));
        }
        num1 = checked ((int) ((long) num1 * 1140671485L + 12820163L & 16777215L));
      }
      projectData.m_rndSeed = num1;
      return (float) num1 / 1.677722E+07f;
    }

    /// <summary>
    /// Initializes the random-number generator.
    /// </summary>
    /// <filterpriority>1</filterpriority>
    public static void Randomize()
    {
      ProjectData projectData = ProjectData.GetProjectData();
      float timer = VBMath.GetTimer();
      int num1 = projectData.m_rndSeed;
      int num2 = BitConverter.ToInt32(BitConverter.GetBytes(timer), 0);
      int num3 = (num2 & (int) ushort.MaxValue ^ num2 >> 16) << 8;
      int num4 = num1 & -16776961 | num3;
      projectData.m_rndSeed = num4;
    }

    /// <summary>
    /// Initializes the random-number generator.
    /// </summary>
    /// <param name="Number">Optional. An Object or any valid numeric expression.</param><filterpriority>1</filterpriority>
    public static void Randomize(double Number)
    {
      ProjectData projectData = ProjectData.GetProjectData();
      int num1 = projectData.m_rndSeed;
      int num2 = !BitConverter.IsLittleEndian ? BitConverter.ToInt32(BitConverter.GetBytes(Number), 0) : BitConverter.ToInt32(BitConverter.GetBytes(Number), 4);
      int num3 = (num2 & (int) ushort.MaxValue ^ num2 >> 16) << 8;
      int num4 = num1 & -16776961 | num3;
      projectData.m_rndSeed = num4;
    }

    private static float GetTimer()
    {
      DateTime now = DateTime.Now;
      return (float) checked ((60 * now.Hour + now.Minute) * 60 + now.Second) + (float) now.Millisecond / 1000f;
    }
  }
}

ProjectData is where the seed is defined, you’ll have to move this elsewhere.

BitConverter is in the System namespace:

Aside from that, it’s pretty straight forward.

Just port it into C#.

1 Like

So I rewrote it to my IRandom interface:

        public class VB_RNG : IRandom
        {

            #region Fields

            private int _seed;

            #endregion

            #region Constructor

            public VB_RNG()
            {
                this.Randomize();
            }

            public VB_RNG(double seed)
            {
                this.Randomize(seed);
            }

            #endregion

            #region Methods
        
            public float Next()
            {
                return this.VBNext(1f);
            }

            public int Next(int size)
            {
                return (int)(this.Next() * size);
            }

            public int Next(int low, int high)
            {
                return (int)(this.Next() * (high - low)) + low;
            }

            public double NextDouble()
            {
                return (double)this.Next();
            }

            public float VBNext(float num)
            {
                int num1 = _seed;
                if ((double)num != 0.0)
                {
                    if ((double)num < 0.0)
                    {
                        long num2 = (long)BitConverter.ToInt32(BitConverter.GetBytes(num), 0) & (long)uint.MaxValue;
                        num1 = checked((int)(num2 + (num2 >> 24) & 16777215L));
                    }
                    num1 = checked((int)((long)num1 * 1140671485L + 12820163L & 16777215L));
                }
                _seed = num1;
                return (float)num1 / 1.677722E+07f;
            }

            public void Randomize()
            {
                DateTime now = DateTime.Now;
                float timer = (float)checked((60 * now.Hour + now.Minute) * 60 + now.Second) + (float)now.Millisecond / 1000f;
                int num1 = _seed;
                int num2 = BitConverter.ToInt32(BitConverter.GetBytes(timer), 0);
                int num3 = (num2 & (int)ushort.MaxValue ^ num2 >> 16) << 8;
                int num4 = num1 & -16776961 | num3;
                _seed = num4;
            }

            public void Randomize(double num)
            {
                int num1 = _seed;
                int num2 = !BitConverter.IsLittleEndian ? BitConverter.ToInt32(BitConverter.GetBytes(num), 0) : BitConverter.ToInt32(BitConverter.GetBytes(num), 4);
                int num3 = (num2 & (int)ushort.MaxValue ^ num2 >> 16) << 8;
                int num4 = num1 & -16776961 | num3;
                _seed = num4;
            }

            #endregion

        }

I use ‘IRandom’ with my RandomUtil, this way I can treat Unity RNG and MS RNG the same… and now I guess VB:

Upon rewriting it I noticed the aglorithm.

It’s a pretty naive algorithm, I once used it back in the day when writing my first RNG:

(note - this git fork is not the original version of this and is not me, the original was on google code and is now lost, and was actually written in like 2008)

I forget the name of the algorithm though.

Anyways… yeah, that’s how’d you go about it.

3 Likes

It depends on the DLL. Some are pure .NET and should therefore be cross-platform (as long as they don’t reference something else which isn’t), some are pure native, and some are a mix. I’m not sure about Microsoft.VisualBasic.dll, though since lordofduct was able to decompile it to ordinary C# code, I’m guessing it’s either pure .NET or at least a mix.

Regardless, if lordofduct’s decompiled code does produce the exact sequence of numbers you’re expecting, I’d also recommend going that route instead, as it significantly reduces the size of your dependency on older technology, and grants you more control over the tools you need.

1 Like

The library seems to have a handful of PInvokes to the user32.dll (which is native), they all seem to be inside the Microsoft.VisualBasic.CompileServices.NativeMethods class.

If you only used the VBMath.Rnd stuff, you wouldn’t touch it.

But… I’d avoid bringing that library in. Even if it was pure .net, Microsoft.VisualBasic.dll has dependencies on things like System.Windows.Forms, and other WinForms specific stuff, which doesn’t like to come into Unity very well. This is because Microsoft.VisualBasic.dll acts as a lilbrary to make VB.Net backwards compatibile with VB6, and that is primarily making .Net WinForms compatible with VB6 Forms.

2 Likes

Thank you lordofduct your code extract worked great! I compared the output with VB6.

How did you reverse engineer the DLL?

JetBrains DotPeek

It decompiles .net dll’s

Thanks. Got it and tried it out. So very cool. This will help tremendously with some legacy code! Big Thank-YOU!