protected static void ReadItems()
    {
        var items =
            from category in pFile.BaseNode["Item"]
            where category.Name != "Pet"
            from itemType in category
            from item in itemType
            select item;

        Items = IterateAllToDict(items, p =>
        {
            var iNode     = p;
            ItemData item = new ItemData();
            int ID        = (int)Utils.ConvertNameToID(iNode.Name);
            item.ID       = ID;

            if (iNode.ContainsChild("info"))
            {
                var infoNode = iNode["info"];
                foreach (var node in infoNode)
                {
                    switch (node.Name)
                    {
                    case "path":
                    case "floatType":
                    case "unitPrice":     // Pricing of recharging???
                    case "icon":
                    case "iconRaw":
                    case "iconReward": break;

                    case "type":
                        item.Type = node.ValueInt8();
                        break;

                    case "price":
                        item.Price = node.ValueInt32();
                        break;

                    case "timeLimited":
                        item.TimeLimited = node.ValueBool();
                        break;

                    case "cash":
                        item.Cash = node.ValueBool();
                        break;

                    case "slotMax":
                        item.MaxSlot = node.ValueUInt16();
                        break;

                    case "meso":
                        item.Mesos = node.ValueInt32();
                        break;

                    case "quest":
                        if (node.ValueBool())
                        {
                            lock (UntradeableDrops)
                            {
                                lock (QuestItems)
                                {
                                    item.IsQuest = true;
                                    UntradeableDrops.Add(item.ID);
                                    QuestItems.Add(item.ID);
                                }
                            }
                        }
                        break;

                    case "success":
                        item.ScrollSuccessRate = node.ValueByte();
                        break;

                    case "cursed":
                        item.ScrollCurseRate = node.ValueByte();
                        break;

                    case "incSTR":
                        item.IncStr = node.ValueByte();
                        break;

                    case "incDEX":
                        item.IncDex = node.ValueByte();
                        break;

                    case "incLUK":
                        item.IncLuk = node.ValueByte();
                        break;

                    case "incINT":
                        item.IncInt = node.ValueByte();
                        break;

                    case "incMHP":
                        item.IncMHP = node.ValueByte();
                        break;

                    case "incMMP":
                        item.IncMMP = node.ValueByte();
                        break;

                    case "pad":
                    case "incPAD":
                        item.IncWAtk = node.ValueByte();
                        break;

                    case "incMAD":
                        item.IncMAtk = node.ValueByte();
                        break;

                    case "incPDD":
                        item.IncWDef = node.ValueByte();
                        break;

                    case "incMDD":
                        item.IncMDef = node.ValueByte();
                        break;

                    case "incACC":
                        item.IncAcc = node.ValueByte();
                        break;

                    case "incEVA":
                        item.IncAvo = node.ValueByte();
                        break;

                    case "incJump":
                        item.IncJump = node.ValueByte();
                        break;

                    case "incSpeed":
                        item.IncSpeed = node.ValueByte();
                        break;

                    case "rate":
                        item.Rate = node.ValueByte();
                        break;

                    case "only":
                        if (node.ValueBool())
                        {
                            lock (UntradeableDrops)
                            {
                                UntradeableDrops.Add(item.ID);
                            }
                        }
                        break;

                    case "time":
                        item.RateTimes = new Dictionary <byte, List <KeyValuePair <byte, byte> > >();
                        foreach (var lNode in node)
                        {
                            string val     = lNode.ValueString();
                            string day     = val.Substring(0, 3);
                            byte hourStart = byte.Parse(val.Substring(4, 2));
                            byte hourEnd   = byte.Parse(val.Substring(7, 2));
                            byte dayid     = 0;

                            switch (day)
                            {
                            case "MON": dayid = 0; break;

                            case "TUE": dayid = 1; break;

                            case "WED": dayid = 2; break;

                            case "THU": dayid = 3; break;

                            case "FRI": dayid = 4; break;

                            case "SAT": dayid = 5; break;

                            case "SUN": dayid = 6; break;

                            case "HOL": dayid = ItemData.HOLIDAY_DAY; break;
                            }
                            if (!item.RateTimes.ContainsKey(dayid))
                            {
                                item.RateTimes.Add(dayid, new List <KeyValuePair <byte, byte> >());
                            }

                            item.RateTimes[dayid].Add(new KeyValuePair <byte, byte>(hourStart, hourEnd));
                        }
                        break;

                    default:
                        Console.WriteLine($"Unhandled item info node {node.Name} for id {item.ID}");
                        break;
                    }
                }
            }
            else
            {
                item.Price   = 0;
                item.Cash    = false;
                item.MaxSlot = 1;
                item.Mesos   = 0;
                item.IsQuest = false;

                item.ScrollSuccessRate = 0;
                item.ScrollCurseRate   = 0;
                item.IncStr            = 0;
                item.IncDex            = 0;
                item.IncInt            = 0;
                item.IncLuk            = 0;
                item.IncMHP            = 0;
                item.IncMMP            = 0;
                item.IncWAtk           = 0;
                item.IncMAtk           = 0;
                item.IncWDef           = 0;
                item.IncMDef           = 0;
                item.IncAcc            = 0;
                item.IncAvo            = 0;
                item.IncJump           = 0;
                item.IncSpeed          = 0;
                item.Rate = 0;
            }
            if (iNode.ContainsChild("spec"))
            {
                var specNode = iNode["spec"];
                foreach (var node in specNode)
                {
                    switch (node.Name)
                    {
                    case "moveTo":
                        item.MoveTo = node.ValueInt32();
                        break;

                    case "hp":
                        item.HP = node.ValueInt16();
                        break;

                    case "mp":
                        item.MP = node.ValueInt16();
                        break;

                    case "hpR":
                        item.HPRate = node.ValueInt16();
                        break;

                    case "mpR":
                        item.MPRate = node.ValueInt16();
                        break;

                    case "speed":
                        item.Speed = node.ValueInt16();
                        break;

                    case "eva":
                        item.Avoidance = node.ValueInt16();
                        break;

                    case "acc":
                        item.Accuracy = node.ValueInt16();
                        break;

                    case "mad":
                        item.MagicAttack = node.ValueInt16();
                        break;

                    case "pad":
                        item.WeaponAttack = node.ValueInt16();
                        break;

                    case "pdd":
                        item.WeaponDefense = node.ValueInt16();
                        break;

                    case "thaw":
                        item.Thaw = node.ValueInt16();
                        break;

                    case "time":
                        item.BuffTime = node.ValueInt32();
                        break;

                    case "curse":
                    case "darkness":
                    case "poison":
                    case "seal":
                    case "weakness":
                        if (node.ValueInt64() != 0)
                        {
                            ItemData.CureFlags flag = 0;
                            switch (node.Name)
                            {
                            case "curse":
                                flag = ItemData.CureFlags.Curse;
                                break;

                            case "darkness":
                                flag = ItemData.CureFlags.Darkness;
                                break;

                            case "poison":
                                flag = ItemData.CureFlags.Poison;
                                break;

                            case "seal":
                                flag = ItemData.CureFlags.Seal;
                                break;

                            case "weakness":
                                flag = ItemData.CureFlags.Weakness;
                                break;
                            }
                            item.Cures |= flag;
                        }
                        break;

                    default:
                        Console.WriteLine($"Unhandled item spec node {node.Name} for id {item.ID}");
                        break;
                    }
                }
            }
            else
            {
                //no spec, continue
                item.MoveTo       = 0;
                item.HP           = 0;
                item.MP           = 0;
                item.HPRate       = 0;
                item.MPRate       = 0;
                item.Speed        = 0;
                item.Avoidance    = 0;
                item.Accuracy     = 0;
                item.MagicAttack  = 0;
                item.WeaponAttack = 0;
                item.BuffTime     = 0;
            }


            if (iNode.ContainsChild("mob")) //summons
            {
                item.Summons = new List <ItemSummonInfo>();

                foreach (var sNode in iNode["mob"])
                {
                    item.Summons.Add(new ItemSummonInfo
                    {
                        MobID  = sNode["id"].ValueInt32(),
                        Chance = sNode["prob"].ValueByte()
                    });
                }
            }
            return(item);
        }, x => x.ID);
    }
    protected static void ReadEquips()
    {
        var equips =
            from category in pFile.BaseNode["Character"]
            where category.Name.EndsWith(".img") == false && category.Name != "Afterimage"
            from item in category
            select new { category.Name, item };

        Equips = IterateAllToDict(equips, p =>
        {
            EquipData eq  = new EquipData();
            var infoBlock = p.item["info"];
            eq.ID         = (int)Utils.ConvertNameToID(p.item.Name);

            foreach (var nxNode in infoBlock)
            {
                switch (nxNode.Name)
                {
                case "islot":
                case "vslot":
                case "icon":
                case "iconRaw":
                case "afterImage":
                case "sfx":
                case "attack":
                case "stand":
                case "walk":
                case "sample":
                case "chatBalloon":
                case "nameTag":
                    break;

                // Nexon typos (= not used!)
                case "incMMD":     // Green Jester, would've been a really good buff!
                case "regPOP":     // Dark Lucida (Female)
                    break;

                case "tuc": eq.Slots = nxNode.ValueByte(); break;

                case "reqLevel": eq.RequiredLevel = nxNode.ValueByte(); break;

                case "reqPOP": eq.RequiredFame = nxNode.ValueByte(); break;

                case "reqDEX": eq.RequiredDexterity = nxNode.ValueUInt16(); break;

                case "reqINT": eq.RequiredIntellect = nxNode.ValueUInt16(); break;

                case "reqLUK": eq.RequiredLuck = nxNode.ValueUInt16(); break;

                case "reqSTR": eq.RequiredStrength = nxNode.ValueUInt16(); break;

                case "reqJob":
                    {
                        var job = nxNode.ValueInt64();
                        // Sake Bottle and Tuna patch; ingame all jobs are required and the flag is -1..

                        if (job == -1)
                        {
                            eq.RequiredJob = 0xFFFF;
                        }
                        else
                        {
                            eq.RequiredJob = (ushort)job;
                        }
                        break;
                    }

                case "price": eq.Price = nxNode.ValueInt32(); break;

                case "incSTR": eq.Strength = nxNode.ValueInt16(); break;

                case "incDEX": eq.Dexterity = nxNode.ValueInt16(); break;

                case "incLUK": eq.Luck = nxNode.ValueInt16();; break;

                case "incINT": eq.Intellect = nxNode.ValueInt16(); break;

                case "incMDD": eq.MagicDefense = nxNode.ValueByte(); break;

                case "incPDD": eq.WeaponDefense = nxNode.ValueByte(); break;

                case "incPAD": eq.WeaponAttack = nxNode.ValueByte(); break;

                case "incMAD": eq.MagicAttack = nxNode.ValueByte(); break;

                case "incSpeed": eq.Speed = nxNode.ValueByte(); break;

                case "incJump": eq.Jump = nxNode.ValueByte(); break;

                case "incACC": eq.Accuracy = nxNode.ValueByte(); break;

                case "incEVA": eq.Avoidance = nxNode.ValueByte(); break;

                case "incMHP": eq.HP = nxNode.ValueInt16(); break;

                case "incMMP": eq.MP = nxNode.ValueInt16(); break;

                case "quest":
                    if (nxNode.ValueBool())
                    {
                        lock (UntradeableDrops)
                        {
                            lock (QuestItems)
                            {
                                QuestItems.Add(eq.ID);
                                if (!UntradeableDrops.Contains(eq.ID))
                                {
                                    UntradeableDrops.Add(eq.ID);
                                }
                            }
                        }
                    }
                    break;

                case "only":
                    if (nxNode.ValueBool())
                    {
                        lock (UntradeableDrops)
                        {
                            if (!UntradeableDrops.Contains(eq.ID))
                            {
                                UntradeableDrops.Add(eq.ID);
                            }
                        }
                    }
                    break;

                case "cash": eq.Cash = nxNode.ValueBool(); break;

                case "attackSpeed": eq.AttackSpeed = nxNode.ValueByte(); break;

                case "knockback": eq.KnockbackRate = nxNode.ValueByte(); break;

                case "timeLimited": eq.TimeLimited = nxNode.ValueBool(); break;

                case "recovery": eq.RecoveryRate = nxNode.ValueFloat(); break;

                default:
                    Console.WriteLine($"Unhandled node {nxNode.Name} for equip {eq.ID}");
                    break;
                }
            }
            return(eq);
        }, x => x.ID, x => x);
    }