The blog of roncli blog
Houston, Texas, United States
CTG Music
Trax in Space Beta
Recent Posts
WTF did Revival Productions just do?
A Tale of Two Communities
The Final Stretch
A Two Tiered, Untiered OTL
Secretly, you wish you could've done what I did
What have I done since v2?
It's Done. It's Finally Done.
The Big Picture is Starting to Wear on Me
A Low Bang to Buck Ratio
February 2005
March 2005
April 2005
May 2005
June 2005
July 2005
August 2005
September 2005
October 2005
November 2005
December 2005
January 2006
February 2006
March 2006
April 2006
May 2006
June 2006
July 2006
August 2006
September 2006
October 2006
November 2006
December 2006
February 2007
March 2007
April 2007
May 2007
June 2007
July 2007
August 2007
September 2007
October 2007
November 2007
December 2007
January 2008
February 2008
March 2008
April 2008
June 2008
July 2008
September 2008
December 2008
February 2009
July 2009
August 2009
September 2009
October 2009
November 2009
February 2010
March 2010
April 2010
June 2010
July 2010
August 2010
September 2010
October 2010
November 2010
December 2010
March 2011
June 2011
July 2011
August 2011
September 2011
October 2011
December 2011
January 2012
February 2012
April 2012
July 2012
November 2012
July 2013
April 2014
July 2014
August 2014
November 2014
December 2014
March 2015
April 2015
May 2015
June 2015
July 2015
September 2015
January 2016
February 2016
May 2016
July 2016
November 2016
March 2017
January 2018
May 2018
June 2018
January 2019
January 2021
February 2021
March 2021
August 2021
October 2021
December 2021
August 2022
November 2022
October 2023
February 2024
April 2024
Current Posts
Thursday, December 18, 2008
LibWowArmory 0.1
Posted: 1:50:00 PM 0 comments
I've finished up the first release of the .Net library I've been working on that queries the WoW Armory. You can grab LibWowArmory from Codeplex. There's still a lot of work I want to do on it, but for now it does what I need it to do, and I can go back and complete the rest of the functionality later.

Labels: , , ,

Friday, December 12, 2008
Using the WoW Armory to determine class talents
Posted: 5:11:00 PM 0 comments
First of all, my apologies for not posting in over two months. My main focus has been working on the Six Minutes To Release website and related functions. Believe me when I say I've poured a lot of time into this.

On the heels of my LibBeImba project, I needed a more robust way to access the World of Warcraft Armory than I have been on the website. My research led me to the fact that they are using XML and XLT files to create their web pages, thus leaving the XML data exposed. In addition, using Fiddler, I have been able to find additional files behind the scenes that allow me to mine even more XML data. Serializing the XML in lieu of an XSD and then presenting the information in a developer-friendly format has led me to create the LibWowArmory project, still in development.

So today I went to take a look at the talents, figuring they were in XML as well. Well, this:

<talentTree value="503501523201322531025012511400200000000000000000000050000000000000000000000000"/>

isn't exact useful data. Turns out that string is parsed via JavaScript, and the talent calculator, instead of being created by XML and XLT, is generated on the client side via JavaScript. This included all of the static data, including the names of the talent trees, the individual talents, and the actual tooltip text you get when you mouse over the talents on the page. Very unArmorylike. And very inaccessable from VB.Net. At least, initially.

A while back I had done a bit of work with compiling .Net assemblies on the fly, and had began to wonder if I could somehow take the JavaScript that Blizzard was using, convert it to some .Net-equivalent language, and mine the data through reflection. After looking into JScript.Net and doing a refresher on Reflection, it only took me a couple of hours to come up with this code:

    Public Sub Talents(ByVal strClass As String)
        ' Gather the source code
        Dim lstSource As New List(Of String)
        lstSource.Add("var jsLoaded; var document; var pointsTier = new Array(); var templateString; var reqTalentID; var reqTalentPoints; var variableIsSite; var maxRank; var theUpdatedRank; var showTip; var hideTip; var textNextRank; var textPoint; var textPoints;")
        Using wcClient As New System.Net.WebClient
            lstSource.Add(wcClient.DownloadString(String.Format("{0}/data.js", strClass.ToLower.Replace(" ", ""))))
            lstSource.Add(wcClient.DownloadString(String.Format("{0}/donotlocalize.js", strClass.ToLower.Replace(" ", ""))))
        End Using

        ' Compile the code
        Dim cdpProvider As CodeDomProvider = CodeDomProvider.CreateProvider("JScript")
        Dim cpParams As New CompilerParameters()
        cpParams.GenerateInMemory = True
        cpParams.GenerateExecutable = True
        Dim crResults As CompilerResults = cdpProvider.CompileAssemblyFromSource(cpParams, lstSource.ToArray())

        ' Run the main module of the assembly
        Dim lstArgs = New List(Of Object)
        Dim objReturn As Object = crResults.CompiledAssembly.EntryPoint.Invoke(Nothing, BindingFlags.Static, Nothing, lstArgs.ToArray(), Nothing)

        ' Get the type in the assembly that contains the data we want
        Dim tType As Type = (From t In crResults.CompiledAssembly.GetTypes() Where t.Name = "JScript 0").First()

        ' Get the tree names
        Dim fiTrees As FieldInfo = (From f In tType.GetFields() Where f.Name = "tree").First()
        Dim lstTrees As List(Of Object) = CType(Unbox(fiTrees.GetValue(Nothing)), List(Of Object))

        ' Get the talents
        Dim fiTalents As FieldInfo = (From f In tType.GetFields() Where f.Name = "talent").First()
        Dim lstTalents As List(Of Object) = CType(Unbox(fiTalents.GetValue(Nothing)), List(Of Object))

        ' Get the rank descriptions (some of these are in HTML format)
        Dim fiRanks As FieldInfo = (From f In tType.GetFields() Where f.Name = "rank").First()
        Dim lstRanks As List(Of Object) = CType(Unbox(fiRanks.GetValue(Nothing)), List(Of Object))

        ' Get the non localizable tree names... used to mine images like so:
        ' String.Format("{0}/images/armory/{1}/background.jpg", strClass.ToLower().Replace(" ", ""), strTree.ToLower().Replace(" ", ""))
        Dim fiNLTrees As FieldInfo = (From f In tType.GetFields() Where f.Name = "nltree").First()
        Dim lstNLTrees As List(Of Object) = CType(Unbox(fiNLTrees.GetValue(Nothing)), List(Of Object))

        ' Get the non localizable talents... used to mine images like so:
        ' String.Format("{0}/images/armory/{1}/{2}.jpg", strClass.ToLower().Replace(" ", ""), strTree.ToLower().Replace(" ", ""), strTalent.ToLower().Replace(" ", ""))
        ' String.Format("{0}/images/armory/{1}/{2}-off.jpg", strClass.ToLower().Replace(" ", ""), strTree.ToLower().Replace(" ", ""), strTalent.ToLower().Replace(" ", "").Replace(":", ""))
        Dim fiNLTalents As FieldInfo = (From f In tType.GetFields() Where f.Name = "nltalent").First()
        Dim lstNLTalents As List(Of Object) = CType(Unbox(fiNLTalents.GetValue(Nothing)), List(Of Object))

        ' TODO: Cache lists here for future use
    End Sub

    Private Function Unbox(ByVal obj As Object) As Object
        If TypeOf obj Is ArrayObject Then
            Dim aoObj = CType(obj, ArrayObject)
            If CInt(aoObj.length) = 0 Then
                Return New List(Of Object)
                Dim arr As Array = CType(Microsoft.JScript.Convert.ToNativeArray(aoObj, Type.GetTypeHandle(New Object)), Array)
                Dim lstReturn As New List(Of Object)
                For Each o As Object In arr
                Return lstReturn
            End If
            Return obj
        End If
    End Function

There's a bit to talk about in here, so I'll go ahead and explain the code.

First, JScript.Net differs from JavaScript in that all variables must be defined. That's essentially what I'm doing with that big long line just after declaring lstSource. All of those variables need to be declared, otherwise the code will not compile. Interesting to note that "document" needs to be declared, since we're not running from within the scope of a web page. Fortunately, I'm not doing anything with the document object, so I'm not going to run into any runtime errors accessing the document.

Next, I download all of the necessary code from Blizzard. The second one has a lot of useless functions, but it does have a couple functions that are used in the last two. Those two files contain all of the talents for the specified class.

The next section compiles the code using the JScript CodeDomProvider. The following section actually runs the main module of the assembly, instantiating all the variables and allowing the functions to run. Most of Blizzard's code is written in the global scope, so most of that code runs. This is where the data from the data.js and donotlocalize.js files are put into memory.

Now we have to use reflection to get the type. After some debugging, I found that the type named "JScript 0" is the one with the data I'm interested in. So, using LINQ, I grab that type from the assembly.

Now we get to pull the data we want. I get the FieldInfo object from the type, again using reflection to make it easy to cherry pick the correct field. Then I can use FieldInfo.GetValue to grab my value and...

Not so fast.

Because the assembly I'm pulling from is in JScript.Net, arrays are not of type System.Array, but rather Microsoft.JScript.ArrayObject. This is a monolithic array type that really doesn't let you play around much with it. Trying to LINQ from it doesn't give you the data, you actually have to call the data through the Item property.

Fortunately, JScript.Net provides a way to change ArrayObjects to normal Arrays, via the ToNativeArray function. I packaged this all up into a recursive function called Unbox. This takes an object and spits the object back out if it's not an ArrayObject. However, if it is an ArrayObject, it converts it into a List(Of Object) using some trickery. Note that it tries to unbox each object that it inserts into the list. This is because Blizzard likes nesting arrays, so I needed to make sure that every ArrayObject inside is put into a more friendly list object.

The idea is to cache this data so I don't have to spam Blizzard for their JavaScript files every time I need to access a player's talents, something I already have a bit of a framework setup to do in LibWowArmory.

The result is I can now take that very ugly talentTree XML node, split the string up into individual characters, and assign each of those numbers to each of the character's talents to learn exactly what their build looks like in code.

All in all, I'm pretty happy with my work on this, and plan to release LibWowArmory soon with item search and lookup, as well as player search, lookup, and talent inspection. Further, I am now able to apply the power of the Armory to my own website in an easy-to-use library in what should be a write-and-forget library, much like LibBeImba turned out to be.

I would be doing this post an injustice if I didn't talk about how the guild is doing. So far, we've cleared about half of Naxxramas 10 as people are still working on leveling up. We've also taken out Sartharion 10 with no drakes, and are farming Archavon 10 when we can as well. Wrath of the Lich King brought some great content into the game, succeeding in many places where, in my opinion, Burning Crusade just failed miserably. I'm really happy with this expansion so far, as is Kathy, and it's something we continue to enjoy together, something that's more important to us than the game itself.

Labels: , , , ,