Page 1 of 2

ISXEQ Crash on Zoning

Posted: Mon Mar 03, 2008 8:52 am
by Rewind
Download the source here.

Hey, everybody. The goal of my post here is to be informative as well as help resolve the issue. The problem is ISXEQ crashing very easily whenever you zone. I am going to provide some very simple examples that you can use to re-create the crashes. The code that I am providing will directly access ISXEQ with no wrapping, so forgive the lengthy lines. I find that it helps to strip everything down to its roots when you're trying to figure out stuff like this, anyways.

Example 1
Ok, so here is the first example. It is very simple. We are only retrieving 2 values using IS. It would seem that the code is well behaved because it verifies that the lavish objects are valid before accessing them, but you will find out later that this is not the case.

You can end the macro in game by doing a /cry which is what I feel like doing sometimes.

Code: Select all

static void Main()
{
    const int emote_cry = 28, emote_cry2 = 162;
    int myAnimation = 0;
    IS.Echo("Crasher loaded.");
    do
    {
        using (new FrameLock(true))
        {
            LavishScriptObject obj = LavishScript.Objects.GetObject("Me");
            if (obj.IsValid)
                myAnimation = obj.GetMember<int>("Animation");
            obj = null;
            
            obj = LavishScript.Objects.GetObject("NearestSpawn", "pc", "radius", "400");
            if (obj.IsValid)
                obj.GetMember<int>("ID");
            obj = null;
        }
        Frame.Wait(false);
    } while (myAnimation != emote_cry && myAnimation != emote_cry2);
    IS.Echo("Crasher exiting.");
}
I have not been able to make example 1 crash ISXEQ while zoning, but it seems that if you go any farther than a hello world you need to be very meticulous and careful how you interact with ISXEQ or you will get the game window replaced with a notepad full of crash barf.

Example 2
Now we are going to start crashing the client. Let's just add a simple for loop in 2 places and zone some to see what happens.

Code: Select all

static void Main()
{
    const int emote_cry = 28, emote_cry2 = 162;
    int myAnimation = 0, y = 0;
    IS.Echo("Crasher loaded.");
    do
    {
        using (new FrameLock(true))
        {
            LavishScriptObject obj = LavishScript.Objects.GetObject("Me");
            for (int i = 0; i < 4000; i++) y = i; //<-- New line
            if (obj.IsValid)
                myAnimation = obj.GetMember<int>("Animation");
            obj = null;
            
            obj = LavishScript.Objects.GetObject("NearestSpawn", "pc", "radius", "400");
            for (int i = 0; i < 4000; i++) y = i; //<-- New line
            if (obj.IsValid)
                obj.GetMember<int>("ID");
            obj = null;
        }
        Frame.Wait(false);
    } while (myAnimation != emote_cry && myAnimation != emote_cry2);
    IS.Echo("Crasher exiting.");
}
For my testing the first zone crashed the .NET program, but IS was able to capture it and just echo the crash info in the console. The second zone crashed the game entirely. Just a subtle change in code made this example completely unstable.

Example 3
Here is another way to crash ISXEQ that I've found while zoning. Let's go back to example 1, but access the disk a little.

Code: Select all

static void Main()
{
    const int emote_cry = 28, emote_cry2 = 162;
    int myAnimation = 0;
    string s = "";
    IS.Echo("Crasher loaded.");
    do
    {
        using (new FrameLock(true))
        {
            LavishScriptObject obj = LavishScript.Objects.GetObject("Me");
            if (obj.IsValid)
                myAnimation = obj.GetMember<int>("Animation");
            obj = null;
            
            obj = LavishScript.Objects.GetObject("NearestSpawn", "pc", "radius", "400");
            if (obj.IsValid)
                obj.GetMember<int>("ID");
            obj = null;
            
            obj = LavishScript.Objects.GetObject("MacroQuest");
            if (obj.IsValid)
                s = obj.GetMember<string>("GameState");
            else
                s = "NOT VALID";
        }
        DebugLogger(s);
        Frame.Wait(false);
    } while (myAnimation != emote_cry && myAnimation != emote_cry2);
    IS.Echo("Crasher exiting.");
}

public static void DebugLogger(string msg)
{
    FileStream file = new FileStream("c:\\debug.txt", FileMode.Append);
    StreamWriter sw = new StreamWriter(file);
    sw.WriteLine(msg);
    sw.Close();
    file.Close();
}
This example crashes the game on the first zone.

Conclusion
All of this could be avoided if there were a reliable way to know when you are zoning. Unfortunately I haven't been able to find one. Nothing that I've tried causes a crash unless you zone. When that happens even a try { } catch { } block won't save you, because it is ISXEQ crashing and not your program.

If anyone knows how to work around this issue, please chime in. I will post a solution here if I can find one.

Posted: Tue Mar 04, 2008 12:27 pm
by Rewind
Ok, so I found a way to stop this. All you gotta do is watch the chat window for the loading message. I had completely forgotten about that.

Code: Select all

using System;
using System.Collections.Generic;
using System.Text;
using LavishScriptAPI;

namespace ISXEQCrashTester
{
    class ChatService
    {
        public ChatService()
        {
            LavishScriptAPI.LavishScript.Events.AttachEventTarget("EQ Chat", GetChat);
        }

        private bool zone = false;

        public bool Zoning
        {
            get { return zone; }
        }

        public void GetChat(object sender, LSEventArgs e)
        {
            string text = Convert.ToString(e.Args[0]);
            if (text == "LOADING, PLEASE WAIT...")
                zone = true;
            else
                zone = false;
        }

        public void Disconnect()
        {
            LavishScriptAPI.LavishScript.Events.DetachEventTarget("EQ Chat", GetChat);
        }

        ~ChatService()
        {
            Disconnect();
        }
    }
}
Then just modify your main loop and you're gold.

Code: Select all

        static void Main()
        {
            const int emote_cry = 28, emote_cry2 = 162;
            int myAnimation = 0, y = 0;
            string s = "";
            ChatService c = new ChatService();

            IS.Echo("Crasher loaded.");
            do
            {
                if (!c.Zoning)
                {
                    using (new FrameLock(true))
                    {
                            //Do stuff
                    }
                }
                Frame.Wait(false);
            } while (myAnimation != emote_cry && myAnimation != emote_cry2);

            c.Disconnect();
            IS.Echo("Crasher exiting.");
        }
I was looking through the source code for ISXEQ today and it seems there is already a zoning service. Does anyone know how to use it in .net? The wiki doesn't help any.

Posted: Tue Mar 04, 2008 11:39 pm
by Rewind
Ok, looks like watching the chat service is not fool proof. It just works most of the time.

Posted: Wed Mar 05, 2008 7:48 pm
by Rewind
Download Source Files

Ok the crash problem seems to be taken care of. I've been testing this for a while and haven't had any crashes using the same tests that would crash on the first zone before. This is basically the same way that MacroQuest monitors its zoning. Here is a template that I've come up with that allows you to build from.

Code: Select all

static class Program
{
    static void Main()
    {
        ZoneService.Activate();

        IS.Echo("Crasher loaded.");

        //Init the data
        ZoningWait();
        using (new FrameLock(true))
        {
            //Do any initialization
        }
        Frame.Wait(false);

        //The main loop
        do
        {
            if (!ZoneService.Zoning)
            {
                using (new FrameLock(true))
                {
                    //Check for zone change.
                    if (ZoneService.Zoned)
                    {
                        //Do whatever

                        //Acknowledge the Zoned flag. This will reset it.
                        ZoneService.AcknowledgeZoned();
                    }

                    //Main part of loop. Do stuff here.
                }
            }
            Frame.Wait(false);
        } while (true); //Make sure you put a way to break out of this.


        //Clean up
        ZoningWait();
        using (new FrameLock(true))
        {
            //Do any cleaning up and finalizing
        }

        ZoneService.Disconnect();

        IS.Echo("Crasher exiting.");
    }

    static void ZoningWait()
    {
        while (ZoneService.Zoning)
        {
            Frame.Wait(false);
        }
    }
}
It's not in the template above, but you'll want to change the check for IsValid to something else:

Code: Select all

obj = LavishScript.Objects.GetObject("NearestSpawn", "pc", "radius", "400");
if (!LavishScriptObject.IsNullOrInvalid(obj))
    obj.GetMember<int>("ID");
obj = null;
It's pretty straightforward and implements a frame loop. You'll notice that I added something called ZoneService. That's a class that monitors the EQ Chat and Zone services through LavishscriptAPI. The EQ Zone service isn't in the live zip yet. You'll have to download the zip file at the top of this post and requild with the modified files (ISXEQ.cpp, ISXEQ.h, and ISXEQServices.cpp). Here is the ZoneService class. It's all in the zip file at the top of the post.

Code: Select all

using System;
using System.Collections.Generic;
using System.Text;
using LavishScriptAPI;

namespace ISXEQCrashTester
{
    public sealed class ZoneService
    {
        //this class is a singleton with lazy initialization
        //see http://msdn2.microsoft.com/en-us/library/ms998558.aspx

        #region Constructors
        private static readonly ZoneService instance = new ZoneService();
        private ZoneService()
        {
            LavishScriptAPI.LavishScript.Events.AttachEventTarget("EQ Chat", GetChat);
            LavishScriptAPI.LavishScript.Events.AttachEventTarget("EQ Zone", GetZone);
        }

        public static ZoneService Instance
        {
            get
            {
                return instance;
            }
        }

        /// <summary>
        /// Call this method at the start of your program to make sure the service is running.
        /// </summary>
        public static void Activate()
        {
            return;
        }

        /// <summary>
        /// Call this method when you are shutting down your program.
        /// </summary>
        public static void Disconnect()
        {
            if (!isDisconnected)
            {
                LavishScriptAPI.LavishScript.Events.DetachEventTarget("EQ Chat", GetChat);
                LavishScriptAPI.LavishScript.Events.DetachEventTarget("EQ Zone", GetZone);
            }
        }

        ~ZoneService()
        {
            Disconnect();
        } 
        #endregion


        // Private variables
        private static bool isDisconnected = false;
        private static bool zoning = false;
        private static bool zoned = false;
        

        public static bool Zoning
        {
            get { return zoning; }
        }

        public static bool Zoned
        {
            get { return zoned; }
        }

        /// <summary>
        /// Acknowledges the zoned event and resets the flag.
        /// <seealso cref="GetZoned"/>
        /// </summary>
        public static void AcknowledgeZoned()
        {
            zoned = false;
        }

        #region Delegates
        /// <summary>
        /// <para>This delegate monitors the "EQ Chat" service for the loading message. This is useful
        /// because many times this will fire before the "EQ Zone" service, and we really need
        /// to be on the ball with this thing to prevent crashes.</para>
        /// <para>In this delegate, we only want to set zoning = true when we get the loading message.
        /// It is advisable to wait for the "EQ Zone" service to set it to false.</para>
        /// </summary>
        public static void GetChat(object sender, LSEventArgs e)
        {
            string text = Convert.ToString(e.Args[0]);
            if (text == "LOADING, PLEASE WAIT...")
                zoning = true;
        }

        /// <summary>
        /// <para>This delegate monitors the "EQ Zone" service.</para>
        /// <list type="bullet">
        /// <listheader>This fires in the following order.</listheader>
        /// <item>
        /// <term>BEGIN_ZONE</term>
        /// <description>The beginning of the zone, duh.</description>
        /// </item>
        /// <item>
        /// <term>END_ZONE</term>
        /// <description>Zone processing ended.</description>
        /// </item>
        /// <item>
        /// <term>ZONED</term>
        /// <description>We have recently zoned. This will remain true until you call the
        /// AcknowledgeZoned() method. THIS TRIGGER DOES NOT FIRE ON EVERY ZONE.</description>
        /// </item>
        /// </list>
        /// </summary>
        public static void GetZone(object sender, LSEventArgs e)
        {
            string text = Convert.ToString(e.Args[0]);
            if (text == "BEGIN_ZONE")
                zoning = true;
            else if (text == "END_ZONE")
            {
                zoning = false;
                zoned = true;
            }
            //The ZONED event does not trigger every time, so we are manually setting it
            //in the END_ZONE event.
            //else if (text == "ZONED")
            //{
            //    zoning = false;
            //    zoned = true;
            //}
        } 
        #endregion

    }
}

Re: ISXEQ Crash on Zoning

Posted: Mon Aug 25, 2025 2:46 am
by xyilla

Re: ISXEQ Crash on Zoning

Posted: Mon Aug 25, 2025 3:23 am
by xyilla

Re: ISXEQ Crash on Zoning

Posted: Mon Aug 25, 2025 3:25 am
by xyilla

Re: ISXEQ Crash on Zoning

Posted: Mon Aug 25, 2025 3:26 am
by xyilla

Re: ISXEQ Crash on Zoning

Posted: Mon Aug 25, 2025 3:27 am
by xyilla

Re: ISXEQ Crash on Zoning

Posted: Mon Aug 25, 2025 3:28 am
by xyilla

Re: ISXEQ Crash on Zoning

Posted: Mon Aug 25, 2025 3:29 am
by xyilla

Re: ISXEQ Crash on Zoning

Posted: Mon Aug 25, 2025 3:30 am
by xyilla

Re: ISXEQ Crash on Zoning

Posted: Mon Aug 25, 2025 3:31 am
by xyilla

Re: ISXEQ Crash on Zoning

Posted: Mon Aug 25, 2025 3:33 am
by xyilla

Re: ISXEQ Crash on Zoning

Posted: Mon Aug 25, 2025 3:34 am
by xyilla