0

Given a DBC file, used by a game, contains records, infos about ingame usable Spells. :) (like ID, name, damage, etc etc...)

To be more complex, the string data is stored in a block after the records. String data in records contain an offset to the string, (so its not really a string)

I have a struct for DBC file, that looks like this:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct SpellEntry
    {
        private const int MAX_EFFECT_INDEX = 3;
        public uint ID;
        public uint Category;
        public uint Dispel;
        public uint Mechanic;
        public uint Attributes;
        public uint AttributesEx;
        public uint AttributesEx2;
        public uint AttributesEx3;
        public uint AttributesEx4;
        public uint AttributesEx5;
        public uint AttributesEx6;
        public uint AttributesEx7;
        public uint Stances;
        public uint unk_320_2;
        public uint StancesNot;
        public uint unk_320_3;
        public uint Targets;
        public uint TargetCreatureType;
        public uint RequiresSpellFocus;
        public uint FacingCasterFlags;
        public uint CasterAuraState;
        public uint TargetAuraState;
        public uint CasterAuraStateNot;
        public uint TargetAuraStateNot;
        public uint casterAuraSpell;
        public uint targetAuraSpell;
        public uint excludeCasterAuraSpell;
        public uint excludeTargetAuraSpell;
        public uint CastingTimeIndex;
        public uint RecoveryTime;
        public uint CategoryRecoveryTime;
        public uint InterruptFlags;
        public uint AuraInterruptFlags;
        public uint ChannelInterruptFlags;
        public uint procFlags;
        public uint procChance;
        public uint procCharges;
        public uint maxLevel;
        public uint baseLevel;
        public uint spellLevel;
        public uint DurationIndex;
        public uint powerType;
        public uint manaCost;
        public uint manaCostPerlevel;
        public uint manaPerSecond;
        public uint manaPerSecondPerLevel;
        public uint rangeIndex;
        public float speed;
        public uint modalNextSpell;
        public uint StackAmount;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.U4)]
        public uint[] Totem;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8, ArraySubType = UnmanagedType.I4)]
        public int[] Reagent;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8, ArraySubType = UnmanagedType.U4)]
        public uint[] ReagentCount;
        public int EquippedItemClass;
        public int EquippedItemSubClassMask;
        public int EquippedItemInventoryTypeMask;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.U4)]
        public uint[] Effect;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.I4)]
        public int[] EffectDieSides;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.I4)]
        public int[] EffectBaseDice;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.R4)]
        public float[] EffectDicePerLevel;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.R4)]
        public float[] EffectRealPointsPerLevel;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.I4)]
        public int[] EffectBasePoints;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.U4)]
        public uint[] EffectMechanic;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.U4)]
        public uint[] EffectImplicitTargetA;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.U4)]
        public uint[] EffectImplicitTargetB;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.U4)]
        public uint[] EffectRadiusIndex;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.U4)]
        public uint[] EffectApplyAuraName;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.U4)]
        public uint[] EffectAmplitude;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.R4)]
        public float[] EffectMultipleValue;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.U4)]
        public uint[] EffectChainTarget;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.U4)]
        public uint[] EffectItemType;
        //[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.I4)]
        //public int[] EffectMiscValue;
        //[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.I4)]
        //public int[] EffectMiscValueB;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.U4)]
        public uint[] EffectTriggerSpell;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.R4)]
        public float[] EffectPointsPerComboPoint;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX)]
        public Flag96[] EffectSpellClassMask;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.U4)]
        public uint[] SpellVisual;
        public uint SpellIconID;
        public uint activeIconID;
        public uint spellPriority;
        [MarshalAs(UnmanagedType.LPStr)]
        public string SpellName;
        [MarshalAs(UnmanagedType.LPStr)]
        public string Rank;
        [MarshalAs(UnmanagedType.LPStr)]
        public string Description;
        [MarshalAs(UnmanagedType.LPStr)]
        public string ToolTip;
        public uint ManaCostPercentage;
        public uint StartRecoveryCategory;
        public uint StartRecoveryTime;
        public uint MaxTargetLevel;
        public uint SpellFamilyName;
        public Flag96 SpellFamilyFlags;
        public uint MaxAffectedTargets;
        public uint DmgClass;
        public uint PreventionType;
        public uint StanceBarOrder;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_EFFECT_INDEX, ArraySubType = UnmanagedType.R4)]
        public float[] DmgMultiplier;
        public uint MinFactionId;
        public uint MinReputation;
        public uint RequiredAuraVision;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.U4)]
        public uint[] TotemCategory;
        public int AreaGroupId;
        public int SchoolMask;
        public uint runeCostID;
        public uint spellMissileID;
        public uint PowerDisplayId;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.R4)]
        public float[] unk_320_4;
        public uint spellDescriptionVariableID;
        public uint SpellDifficultyId;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct Flag96
    {
        public uint DwA;
        public uint DwB;
        public uint DwC;

        public override string ToString()
        {
            return string.Format("DwA: {0} - DwB: {1} - DwC: {2}", DwA, DwB, DwC);
        }
    }

So, what I'm trying to do is marshaling bytes to this struct with the following code:

                byte[] buff = new byte[Marshal.SizeOf(typeof(Spell.SpellEntry))];
                binReader.BaseStream.Seek(DBCFile.HEADER_SIZE + (index * 4 * 234), SeekOrigin.Begin);
                var bytes = binReader.ReadBytes(buff.Length);  
                var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
                var obj = (Spell.SpellEntry)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),typeof(Spell.SpellEntry));
                handle.Free();

Which gives me an AccessViolationException on the Marshal.PtrToStructure line. I suspect my struct is invalid, somehow, could you correct me, or give a hint? What's interesting that when I debug in the IDE, it looks like its reads the structure correctly, but gives this exception... weird.

I also tryed reading into struct one by one, like

                    spell[index].ID = BitConverter.ToUInt32(binReader.ReadBytes(4), 0);
                    spell[index].Category = BitConverter.ToUInt32(binReader.ReadBytes(4), 0);
                    spell[index].Dispel = BitConverter.ToUInt32(binReader.ReadBytes(4), 0);
                    spell[index].Mechanic = BitConverter.ToUInt32(binReader.ReadBytes(4), 0);

which is working 100% good for me, but its looks lame, and its really long in the code. Marshaling looks more pro :) So, whats your opinion, Is marshaling faster than reading one by one, if yes, how should I fix it? THanks

Dominik Antal
  • 3,281
  • 4
  • 34
  • 50
  • Marshalling is for marshalling data between managed and unmanaged code, not to provide a convenient way to read data into structs. A [BinaryReader](http://msdn.microsoft.com/en-us/library/system.io.binaryreader.aspx) is appropriate here. – dtb Nov 22 '10 at 19:53
  • Yes, thats the other way I do it, but on a big struct like this, is it the correct, fastes method? – Dominik Antal Nov 22 '10 at 19:59
  • I'd say BinaryReader is the right solution here. I haven't checked what's faster. Just because marshalling requires you to type less lines of code, it doesn't mean that it must be faster (there's *a lot* going on behind the scenes; marshalling isn't just a memcpy). Use a profiler. – dtb Nov 22 '10 at 20:13
  • I found a really similar post here http://stackoverflow.com/questions/2384/read-binary-file-into-a-struct-c and another here http://www.codeproject.com/KB/files/fastbinaryfileinput.aspx – Dominik Antal Nov 22 '10 at 20:15
  • It says "at about 40 fields, the results for the three approaches were almost equivalent, and beyond that, the block reading approaches gained an upper hand." And on the block reading example, he using this snippet : http://pastebin.com/CUA9A2UZ (and that is whay iam doing, and I get the erroe :/) – Dominik Antal Nov 22 '10 at 20:18

1 Answers1

1
    var bytes = binReader.ReadBytes(buff.Length);

buff.Length falls from the sky in that snippet. It has to be at least as large, if not exactly as large as Marshal.SizeOf(typeof(Spell.SpellEntry)). Furthermore, if BitConverter works then you have to set the Pack property of the [StructLayout] attribute to 1. Once you've got the buffer length problem sorted out you can find declaration problems by checking the fields for the first mangled value.

A generic solution is available in my answer here.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Sorry, forgot this line. byte[] buff = new byte[Marshal.SizeOf(typeof(Spell.SpellEntry))]; – Dominik Antal Nov 22 '10 at 20:22
  • Will try your advices, I'll report :) THanks – Dominik Antal Nov 22 '10 at 20:23
  • Btw, [MarshalAs(UnmanagedType.LPStr)] can never work either, the file cannot contain pointers. It will bomb. – Hans Passant Nov 22 '10 at 21:02
  • You're my hero! :) I hope I can do this now. Btw the struct is not made by me, Iam just using it, this Is the first time I see these layout things, but time to learn. – Dominik Antal Nov 22 '10 at 21:04
  • How would you solve this? Because the "strings" in the structs, are not strings when I read them! Theyre numbers, pointing to the string in the stirngtable. I used to solve this as the following, when I was reading this file value by value, so: Int32 SpellNamePtr = BitConverter.ToInt32(binReader.ReadBytes(64), 0); if (SpellNamePtr != 0) spell[index].SpellName = GetString(SpellNamePtr); And this worked great, but now with marshaling, It wont :/ You have any idea? Thanks for the big helps! – Dominik Antal Nov 22 '10 at 21:09
  • GetString() method was just get the string of a string array, that I already filled with all strings from the stringtable. – Dominik Antal Nov 22 '10 at 21:12
  • The file format is just not very suitable as a runtime format. A workaround is to add properties to call the GetString() method. – Hans Passant Nov 22 '10 at 21:14
  • Can you help me with that please, Iam not that experianced :/ This struct was given me by someone else, that I cant ask. Btw checked, and yes its "crashes" on [MarshalAs(UnmanagedType.LPStr)] public string SpellName; – Dominik Antal Nov 22 '10 at 21:16