OSDN Git Service

Fleetクラスで艦隊編成を行いShipStatusのリストを構築する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / Logger.cs
index 3674899..341b8a0 100644 (file)
@@ -1,10 +1,25 @@
-using System;\r
+// Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
+//\r
+// Licensed under the Apache License, Version 2.0 (the "License");\r
+// you may not use this file except in compliance with the License.\r
+// You may obtain a copy of the License at\r
+//\r
+//    http://www.apache.org/licenses/LICENSE-2.0\r
+//\r
+// Unless required by applicable law or agreed to in writing, software\r
+// distributed under the License is distributed on an "AS IS" BASIS,\r
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+// See the License for the specific language governing permissions and\r
+// limitations under the License.\r
+\r
+using System;\r
 using System.Collections.Generic;\r
+using System.Globalization;\r
 using System.IO;\r
-using System.Text;\r
 using System.Linq;\r
-using System.Web;\r
-using System.Windows.Forms;\r
+using System.Text;\r
+using KancolleSniffer.Model;\r
+using KancolleSniffer.Util;\r
 \r
 namespace KancolleSniffer\r
 {\r
@@ -12,45 +27,58 @@ namespace KancolleSniffer
     public enum LogType\r
     {\r
         None = 0,\r
-        Mission = 1,\r
-        Battle = 2,\r
-        Material = 4,\r
-        CreateItem = 8,\r
-        CreateShip = 16,\r
-        All = 31,\r
+        Mission = 1 << 0,\r
+        Battle = 1 << 1,\r
+        Material = 1 << 2,\r
+        CreateItem = 1 << 3,\r
+        CreateShip = 1 << 4,\r
+        RemodelSlot = 1 << 5,\r
+        Achivement = 1 << 6,\r
+        All = (1 << 7) - 1\r
     }\r
 \r
     public class Logger\r
     {\r
         private LogType _logType;\r
-        private readonly ShipMaster _shipMaster;\r
         private readonly ShipInfo _shipInfo;\r
         private readonly ItemInfo _itemInfo;\r
+        private readonly BattleInfo _battleInfo;\r
         private Action<string, string, string> _writer;\r
         private Func<DateTime> _nowFunc;\r
-        private const string DateTimeFormat = @"yyyy\-MM\-dd HH\:mm\:ss";\r
+        public const string DateTimeFormat = @"yyyy\-MM\-dd HH\:mm\:ss";\r
         private dynamic _battle;\r
         private dynamic _map;\r
         private dynamic _basic;\r
         private int _kdockId;\r
         private DateTime _prevTime;\r
+        private int[] _currentMaterial = new int[Enum.GetValues(typeof(Material)).Length];\r
         private int _materialLogInterval = 10;\r
+        private bool _start;\r
+        private int _lastExp = -1;\r
+        private DateTime _lastDate;\r
+        private DateTime _endOfMonth;\r
+        private DateTime _nextDate;\r
 \r
         public int MaterialLogInterval\r
         {\r
-            set { _materialLogInterval = value; }\r
+            set => _materialLogInterval = value;\r
         }\r
 \r
         public string OutputDir\r
         {\r
-            set { _writer = new LogWriter(value).Write; }\r
+            set => _writer = new LogWriter(value).Write;\r
         }\r
 \r
-        public Logger(ShipMaster master, ShipInfo ship, ItemInfo item)\r
+        public static string FormatDateTime(DateTime date)\r
+        {\r
+            return date.ToString(DateTimeFormat, CultureInfo.InvariantCulture);\r
+        }\r
+\r
+        public Logger(ShipInfo ship, ItemInfo item, BattleInfo battle)\r
         {\r
-            _shipMaster = master;\r
             _shipInfo = ship;\r
             _itemInfo = item;\r
+            _battleInfo = battle;\r
             _writer = new LogWriter().Write;\r
             _nowFunc = () => DateTime.Now;\r
         }\r
@@ -66,6 +94,11 @@ namespace KancolleSniffer
             _nowFunc = nowFunc;\r
         }\r
 \r
+        public void FlashLog()\r
+        {\r
+            FlashAchivementLog();\r
+        }\r
+\r
         public void InspectMissionResult(dynamic json)\r
         {\r
             var r = (int)json.api_clear_result;\r
@@ -83,80 +116,214 @@ namespace KancolleSniffer
                 if (flag == 1)\r
                     material[(int)Material.Bucket] = count;\r
                 else if (flag == 2)\r
-                    material[(int)Material.Burner + 2] = count;  // 高速建造材と開発資材が反対なのでいつか直す\r
+                    material[(int)Material.Burner + 2] = count; // 高速建造材と開発資材が反対なのでいつか直す\r
                 else if (flag == 3)\r
-                    material[(int)Material.Development -2] = count;\r
+                    material[(int)Material.Development - 2] = count;\r
             }\r
             if ((_logType & LogType.Mission) != 0)\r
             {\r
                 _writer("遠征報告書",\r
-                    string.Join(",", _nowFunc().ToString(DateTimeFormat),\r
+                    string.Join(",", FormatDateTime(_nowFunc()),\r
                         rstr, json.api_quest_name, string.Join(",", material)),\r
                     "日付,結果,遠征,燃料,弾薬,鋼材,ボーキ,開発資材,高速修復材,高速建造材");\r
             }\r
         }\r
 \r
-        public void InspectMap(dynamic json)\r
+        public void InspectMapStart(dynamic json)\r
         {\r
+            _start = true;\r
             _map = json;\r
+            _battle = null;\r
+            if ((_logType & LogType.Material) != 0)\r
+                WriteMaterialLog(_nowFunc());\r
         }\r
 \r
-        public void InspectBattle(dynamic json)\r
+        public void InspectMapNext(dynamic json)\r
+        {\r
+            if ((_logType & LogType.Achivement) != 0 && json.api_get_eo_rate() && (int)json.api_get_eo_rate != 0)\r
+            {\r
+                _writer("戦果",\r
+                    FormatDateTime(_nowFunc()) + "," + _lastExp + "," + (int)json.api_get_eo_rate,\r
+                    "日付,経験値,EO");\r
+            }\r
+            _map = json;\r
+        }\r
+\r
+        public void InspectClearItemGet(dynamic json)\r
         {\r
-            if (!IsNightBattle(json))\r
-                _battle = json;\r
+            if ((_logType & LogType.Achivement) == 0)\r
+                return;\r
+            if (!json.api_bounus())\r
+                return;\r
+            foreach (var entry in json.api_bounus)\r
+            {\r
+                if (entry.api_type != 18)\r
+                    continue;\r
+                _writer("戦果",\r
+                    FormatDateTime(_nowFunc()) + "," + _lastExp + "," + (int)entry.api_count,\r
+                    "日付,経験値,EO");\r
+                break;\r
+            }\r
         }\r
 \r
-        private bool IsNightBattle(dynamic json)\r
+        public void InspectBattle(dynamic json)\r
         {\r
-            return json.api_hougeki();\r
+            if (_battle != null) // 通常の夜戦は無視する\r
+                return;\r
+            _battle = json;\r
         }\r
 \r
         public void InspectBattleResult(dynamic result)\r
         {\r
+            if ((_logType & LogType.Achivement) != 0 && result.api_get_exmap_rate())\r
+            {\r
+                var rate = result.api_get_exmap_rate is string\r
+                    ? int.Parse(result.api_get_exmap_rate)\r
+                    : (int)result.api_get_exmap_rate;\r
+                if (rate != 0)\r
+                {\r
+                    _writer("戦果", FormatDateTime(_nowFunc()) + "," + _lastExp + "," + rate,\r
+                        "日付,経験値,EO");\r
+                }\r
+            }\r
             if ((_logType & LogType.Battle) == 0 || _map == null || _battle == null)\r
             {\r
                 _map = _battle = null;\r
                 return;\r
             }\r
-            var fships = new List<string>();\r
-            var deck = _shipInfo.GetDeck(_battle.api_dock_id() ? (int)_battle.api_dock_id - 1 : 0);\r
-            fships.AddRange(deck.Select(id =>\r
+            var fships = GenerateFirendShipList();\r
+            var eships = GenerateEnemyShipList();\r
+            var cell = (int)_map.api_no;\r
+            var boss = "";\r
+            if (_start)\r
+                boss = "出撃";\r
+            if (cell == (int)_map.api_bosscell_no || (int)_map.api_event_id == 5)\r
+                boss = _start ? "出撃&ボス" : "ボス";\r
+            var dropType = result.api_get_ship() ? result.api_get_ship.api_ship_type : "";\r
+            if (result.api_get_useitem())\r
             {\r
-                if (id == -1)\r
-                    return ",";\r
-                var s = _shipInfo[id];\r
-                return string.Format("{0}(Lv{1}),{2}/{3}", s.Name, s.Level, s.NowHp, s.MaxHp);\r
-            }));\r
-            var edeck = ((int[])_battle.api_ship_ke).Skip(1).ToArray();\r
-            var enowhp = ((int[])_battle.api_nowhps).Skip(7).ToArray();\r
-            var emaxhp = ((int[])_battle.api_maxhps).Skip(7).ToArray();\r
-            var eships = new List<string>();\r
-            for (var i = 0; i < edeck.Count(); i++)\r
+                if (dropType == "")\r
+                    dropType = "アイテム";\r
+                else\r
+                    dropType += "+アイテム";\r
+            }\r
+            var dropName = result.api_get_ship() ? result.api_get_ship.api_ship_name : "";\r
+            if (result.api_get_useitem())\r
             {\r
-                eships.Add(edeck[i] == -1\r
-                    ? ","\r
-                    : string.Format("{0},{1}/{2}", _shipMaster[edeck[i]].Name, enowhp[i], emaxhp[i]));\r
+                var itemName = _itemInfo.GetUseItemName((int)result.api_get_useitem.api_useitem_id);\r
+                if (dropName == "")\r
+                    dropName = itemName;\r
+                else\r
+                    dropName += "+" + itemName;\r
             }\r
-            var cell = (int)_map.api_no;\r
-            var boss = cell == (int)_map.api_bosscell_no || (int)_map.api_event_id == 5 ? "ボス" : "";\r
-            _writer("海戦・ドロップ報告書", string.Join(",", _nowFunc().ToString(DateTimeFormat),\r
-                result.api_quest_name,\r
-                cell, boss,\r
-                result.api_win_rank,\r
-                BattleFormationName((int)_battle.api_formation[2]),\r
-                FormationName(_battle.api_formation[0]),\r
-                FormationName(_battle.api_formation[1]),\r
-                result.api_enemy_info.api_deck_name,\r
-                result.api_get_ship() ? result.api_get_ship.api_ship_type : "",\r
-                result.api_get_ship() ? result.api_get_ship.api_ship_name : "",\r
-                string.Join(",", fships),\r
-                string.Join(",", eships)),\r
+            var fp = _shipInfo.Fleets[BattleInfo.DeckId(_battle)].FighterPower;\r
+            var fpower = fp[0] == fp[1] ? fp[0].ToString() : fp[0] + "~" + fp[1];\r
+            _writer("海戦・ドロップ報告書", string.Join(",", FormatDateTime(_nowFunc()),\r
+                    result.api_quest_name,\r
+                    cell, boss,\r
+                    result.api_win_rank,\r
+                    BattleFormationName((int)_battle.api_formation[2]),\r
+                    FormationName(_battle.api_formation[0]),\r
+                    FormationName(_battle.api_formation[1]),\r
+                    result.api_enemy_info.api_deck_name,\r
+                    dropType, dropName,\r
+                    string.Join(",", fships),\r
+                    string.Join(",", eships),\r
+                    fpower, _battleInfo.EnemyFighterPower.AirCombat + _battleInfo.EnemyFighterPower.UnknownMark,\r
+                    AirControlLevelName(_battle)),\r
                 "日付,海域,マス,ボス,ランク,艦隊行動,味方陣形,敵陣形,敵艦隊,ドロップ艦種,ドロップ艦娘," +\r
                 "味方艦1,味方艦1HP,味方艦2,味方艦2HP,味方艦3,味方艦3HP,味方艦4,味方艦4HP,味方艦5,味方艦5HP,味方艦6,味方艦6HP," +\r
-                "敵艦1,敵艦1HP,敵艦2,敵艦2HP,敵艦3,敵艦3HP,敵艦4,敵艦4HP,敵艦5,敵艦5HP,敵艦6,敵艦6HP"\r
-                );\r
+                "敵艦1,敵艦1HP,敵艦2,敵艦2HP,敵艦3,敵艦3HP,敵艦4,敵艦4HP,敵艦5,敵艦5HP,敵艦6,敵艦6HP," +\r
+                "味方制空値,敵制空値,制空状態"\r
+            );\r
             _map = _battle = null;\r
+            _start = false;\r
+        }\r
+\r
+        private IEnumerable<string> GenerateFirendShipList()\r
+        {\r
+            int deckId = BattleInfo.DeckId(_battle);\r
+            if (_battle.api_f_nowhps_combined())\r
+            {\r
+                var mainShips = _shipInfo.Fleets[0].Ships;\r
+                var guardShips = _shipInfo.Fleets[1].Ships;\r
+                return mainShips.Zip(guardShips, (main, guard) =>\r
+                {\r
+                    if (main.Empty && guard.Empty)\r
+                        return ",";\r
+                    var name = "";\r
+                    var hp = "";\r
+                    if (!main.Empty)\r
+                    {\r
+                        name = $"{main.Name}(Lv{main.Level})";\r
+                        hp = $"{main.NowHp}/{main.MaxHp}";\r
+                    }\r
+                    name += "・";\r
+                    hp += "・";\r
+                    if (!guard.Empty)\r
+                    {\r
+                        name += $"{guard.Name}(Lv{guard.Level})";\r
+                        hp += $"{guard.NowHp}/{guard.MaxHp}";\r
+                    }\r
+                    return name + "," + hp;\r
+                }).ToList();\r
+            }\r
+            var ships = _shipInfo.Fleets[deckId].Ships;\r
+            if (ships.Count > 6)\r
+            {\r
+                var result = new List<string>();\r
+                for (var i = 0; i < 12 - ships.Count; i++)\r
+                {\r
+                    var ship = ships[i];\r
+                    result.Add($"{ship.Name}(Lv{ship.Level}),{ship.NowHp}/{ship.MaxHp}");\r
+                }\r
+                for (var i = 0; i < ships.Count - 6; i++)\r
+                {\r
+                    var s1 = ships[12 - ships.Count + i];\r
+                    var s2 = ships[6 + i];\r
+                    result.Add(\r
+                        $"{s1.Name}(Lv{s1.Level})・{s2.Name}(Lv{s2.Level})," +\r
+                        $"{s1.NowHp}/{s1.MaxHp}・{s2.NowHp}/{s2.MaxHp}");\r
+                }\r
+                return result;\r
+            }\r
+            return ships.Select(ship =>\r
+            {\r
+                if (ship.Empty)\r
+                    return ",";\r
+                return $"{ship.Name}(Lv{ship.Level}),{ship.NowHp}/{ship.MaxHp}";\r
+            }).ToList();\r
+        }\r
+\r
+        private IEnumerable<string> GenerateEnemyShipList()\r
+        {\r
+            var result = _battleInfo.Result.Enemy.Main.Concat(Enumerable.Repeat(new ShipStatus(), 6)).Take(6);\r
+            if (_battleInfo.Result.Enemy.Guard.Length == 0)\r
+            {\r
+                return result.Select(s => s.Empty ? "," : $"{s.Name},{s.NowHp}/{s.MaxHp}").ToList();\r
+            }\r
+            var mainShips = result;\r
+            var guardShips = _battleInfo.Result.Enemy.Guard.Concat(Enumerable.Repeat(new ShipStatus(), 6)).Take(6);\r
+            return mainShips.Zip(guardShips, (main, guard) =>\r
+            {\r
+                if (main.Empty && guard.Empty)\r
+                    return ",";\r
+                var name = "";\r
+                var hp = "";\r
+                if (!main.Empty)\r
+                {\r
+                    name = $"{main.Name}";\r
+                    hp = $"{main.NowHp}/{main.MaxHp}";\r
+                }\r
+                name += "・";\r
+                hp += "・";\r
+                if (!guard.Empty)\r
+                {\r
+                    name += $"{guard.Name}";\r
+                    hp += $"{guard.NowHp}/{guard.MaxHp}";\r
+                }\r
+                return name + "," + hp;\r
+            }).ToList();\r
         }\r
 \r
         private string FormationName(dynamic f)\r
@@ -175,6 +342,8 @@ namespace KancolleSniffer
                     return "梯形陣";\r
                 case 5:\r
                     return "単横陣";\r
+                case 6:\r
+                    return "警戒陣";\r
                 case 11:\r
                     return "第一警戒航行序列";\r
                 case 12:\r
@@ -188,7 +357,7 @@ namespace KancolleSniffer
             }\r
         }\r
 \r
-        private static String BattleFormationName(int f)\r
+        private static string BattleFormationName(int f)\r
         {\r
             switch (f)\r
             {\r
@@ -205,9 +374,79 @@ namespace KancolleSniffer
             }\r
         }\r
 \r
+        private string AirControlLevelName(dynamic json)\r
+        {\r
+            // BattleInfo.AirControlLevelは夜戦で消されているかもしれないので、こちらで改めて調べる。\r
+            if (!json.api_kouku())\r
+                return "";\r
+            var stage1 = json.api_kouku.api_stage1;\r
+            if (stage1 == null)\r
+                return "";\r
+            if (stage1.api_f_count == 0 && stage1.api_e_count == 0)\r
+                return "";\r
+            switch ((int)stage1.api_disp_seiku)\r
+            {\r
+                case 0:\r
+                    return "航空均衡";\r
+                case 1:\r
+                    return "制空権確保";\r
+                case 2:\r
+                    return "航空優勢";\r
+                case 3:\r
+                    return "航空劣勢";\r
+                case 4:\r
+                    return "制空権喪失";\r
+                default:\r
+                    return "";\r
+            }\r
+        }\r
+\r
         public void InspectBasic(dynamic json)\r
         {\r
             _basic = json;\r
+            if ((_logType & LogType.Achivement) == 0)\r
+                return;\r
+            var now = _nowFunc();\r
+            var exp = (int)json.api_experience;\r
+            var isNewMonth = _endOfMonth == DateTime.MinValue || now.CompareTo(_endOfMonth) >= 0;\r
+            var isNewDate = _nextDate == DateTime.MinValue || now.CompareTo(_nextDate) >= 0;\r
+            if (isNewDate || isNewMonth)\r
+            {\r
+                if (_lastDate != DateTime.MinValue)\r
+                {\r
+                    _writer("戦果", FormatDateTime(_lastDate) + "," + _lastExp + ",0", "日付,経験値,EO");\r
+                }\r
+                _writer("戦果", FormatDateTime(now) + "," + exp + ",0", "日付,経験値,EO");\r
+                if (isNewMonth)\r
+                {\r
+                    _endOfMonth = new DateTime(now.Year, now.Month, DateTime.DaysInMonth(now.Year, now.Month),\r
+                        22, 0, 0);\r
+                    if (_endOfMonth.CompareTo(now) <= 0)\r
+                    {\r
+                        var days = _endOfMonth.Month == 12\r
+                            ? DateTime.DaysInMonth(_endOfMonth.Year + 1, 1)\r
+                            : DateTime.DaysInMonth(_endOfMonth.Year, _endOfMonth.Month);\r
+                        _endOfMonth = _endOfMonth.AddDays(days);\r
+                    }\r
+                }\r
+                _nextDate = new DateTime(now.Year, now.Month, now.Day, 2, 0, 0);\r
+                if (now.Hour >= 2)\r
+                    _nextDate = _nextDate.AddDays(1);\r
+                if (_nextDate.Day == 1)\r
+                    _nextDate = _nextDate.AddDays(1);\r
+            }\r
+            _lastDate = now;\r
+            _lastExp = exp;\r
+        }\r
+\r
+        private void FlashAchivementLog()\r
+        {\r
+            if ((_logType & LogType.Achivement) == 0)\r
+                return;\r
+            if (_lastDate != DateTime.MinValue)\r
+            {\r
+                _writer("戦果", FormatDateTime(_lastDate) + "," + _lastExp + ",0", "日付,経験値,EO");\r
+            }\r
         }\r
 \r
         public void InspectCreateItem(string request, dynamic json)\r
@@ -224,7 +463,7 @@ namespace KancolleSniffer
                 type = spec.TypeName;\r
             }\r
             _writer("開発報告書",\r
-                _nowFunc().ToString(DateTimeFormat) + "," +\r
+                FormatDateTime(_nowFunc()) + "," +\r
                 string.Join(",", name, type,\r
                     values["api_item1"], values["api_item2"], values["api_item3"], values["api_item4"],\r
                     Secretary(), _basic.api_level),\r
@@ -243,10 +482,10 @@ namespace KancolleSniffer
                 return;\r
             var kdock = ((dynamic[])json).First(e => e.api_id == _kdockId);\r
             var material = Enumerable.Range(1, 5).Select(i => (int)kdock["api_item" + i]).ToArray();\r
-            var ship = _shipMaster[(int)kdock.api_created_ship_id];\r
+            var ship = _shipInfo.GetSpec((int)kdock.api_created_ship_id);\r
             var avail = ((dynamic[])json).Count(e => (int)e.api_state == 0);\r
             _writer("建造報告書",\r
-                _nowFunc().ToString(DateTimeFormat) + "," +\r
+                FormatDateTime(_nowFunc()) + "," +\r
                 string.Join(",", material.First() >= 1500 ? "大型艦建造" : "通常艦建造",\r
                     ship.Name, ship.ShipTypeName, string.Join(",", material), avail, Secretary(), _basic.api_level),\r
                 "日付,種類,名前,艦種,燃料,弾薬,鋼材,ボーキ,開発資材,空きドック,秘書艦,司令部Lv");\r
@@ -255,7 +494,7 @@ namespace KancolleSniffer
 \r
         private string Secretary()\r
         {\r
-            var ship = _shipInfo.GetShipStatuses(0)[0];\r
+            var ship = _shipInfo.Fleets[0].Ships[0];\r
             return ship.Name + "(" + ship.Level + ")";\r
         }\r
 \r
@@ -263,18 +502,65 @@ namespace KancolleSniffer
         {\r
             if ((_logType & LogType.Material) == 0)\r
                 return;\r
+            foreach (var e in json)\r
+                _currentMaterial[(int)e.api_id - 1] = (int)e.api_value;\r
             var now = _nowFunc();\r
             if (now - _prevTime < TimeSpan.FromMinutes(_materialLogInterval))\r
                 return;\r
+            WriteMaterialLog(now);\r
+        }\r
+\r
+        public void WriteMaterialLog(DateTime now)\r
+        {\r
             _prevTime = now;\r
-            var material = new int[8];\r
-            foreach (var e in json)\r
-                material[(int)e.api_id - 1] = (int)e.api_value;\r
             _writer("資材ログ",\r
-                now.ToString(DateTimeFormat) + "," +\r
-                string.Join(",", material) + ",",\r
+                FormatDateTime(now) + "," +\r
+                string.Join(",", _currentMaterial),\r
                 "日付,燃料,弾薬,鋼材,ボーキ,高速建造材,高速修復材,開発資材,改修資材");\r
         }\r
+\r
+        public void SetCurrentMaterial(int[] material)\r
+        {\r
+            _currentMaterial = material;\r
+        }\r
+\r
+        public void InspectRemodelSlot(string request, dynamic json)\r
+        {\r
+            if ((_logType & LogType.RemodelSlot) == 0)\r
+                return;\r
+            var now = _nowFunc();\r
+            var values = HttpUtility.ParseQueryString(request);\r
+            var id = int.Parse(values["api_slot_id"]);\r
+            var name = _itemInfo.GetName(id);\r
+            var level = _itemInfo.GetStatus(id).Level;\r
+            var success = (int)json.api_remodel_flag == 1 ? "○" : "×";\r
+            var certain = int.Parse(values["api_certain_flag"]) == 1 ? "○" : "";\r
+            var useName = "";\r
+            var useNum = "";\r
+            if (json.api_use_slot_id())\r
+            {\r
+                var use = (int[])json.api_use_slot_id;\r
+                useName = _itemInfo.GetName(use[0]);\r
+                useNum = use.Length.ToString();\r
+            }\r
+            var after = (int[])json.api_after_material;\r
+            var diff = new int[after.Length];\r
+            for (var i = 0; i < after.Length; i++)\r
+                diff[i] = _currentMaterial[i] - after[i];\r
+            var ship1 = Secretary();\r
+            var ship2 = "";\r
+            var ships = _shipInfo.Fleets[0].Ships;\r
+            if (!ships[1].Empty)\r
+                ship2 = ships[1].Name + "(" + ships[1].Level + ")";\r
+            _writer("改修報告書",\r
+                FormatDateTime(now) + "," +\r
+                string.Join(",", name, level, success, certain, useName, useNum,\r
+                    diff[(int)Material.Fuel], diff[(int)Material.Bullet], diff[(int)Material.Steal],\r
+                    diff[(int)Material.Bouxite],\r
+                    diff[(int)Material.Development], diff[(int)Material.Screw],\r
+                    ship1, ship2),\r
+                "日付,改修装備,レベル,成功,確実化,消費装備,消費数,燃料,弾薬,鋼材,ボーキ,開発資材,改修資材,秘書艦,二番艦");\r
+        }\r
     }\r
 \r
     public class LogWriter\r
@@ -295,10 +581,7 @@ namespace KancolleSniffer
             // Shift_JISでないとExcelで文字化けする\r
             private readonly Encoding _encoding = Encoding.GetEncoding("Shift_JIS");\r
 \r
-            public string ReadAllText(string path)\r
-            {\r
-                return File.ReadAllText(path, _encoding);\r
-            }\r
+            public string ReadAllText(string path) => File.ReadAllText(path, _encoding);\r
 \r
             public void AppendAllText(string path, string text)\r
             {\r
@@ -310,15 +593,12 @@ namespace KancolleSniffer
                 File.Delete(path);\r
             }\r
 \r
-            public bool Exists(string path)\r
-            {\r
-                return File.Exists(path);\r
-            }\r
+            public bool Exists(string path) => File.Exists(path);\r
         }\r
 \r
         public LogWriter(string outputDir = null, IFile file = null)\r
         {\r
-            _outputDir = outputDir ?? Path.GetDirectoryName(Application.ExecutablePath);\r
+            _outputDir = outputDir ?? AppDomain.CurrentDomain.BaseDirectory;\r
             _file = file ?? new FileWrapper();\r
         }\r
 \r
@@ -347,10 +627,27 @@ namespace KancolleSniffer
                     _file.AppendAllText(f, s + "\r\n");\r
                     break;\r
                 }\r
-                catch (IOException)\r
+                catch (IOException e)\r
                 {\r
+                    if (f == tmp)\r
+                        throw new LogIOException("報告書の出力中にエラーが発生しました。", e);\r
                 }\r
             }\r
         }\r
     }\r
+\r
+    public class LogIOException : Exception\r
+    {\r
+        public LogIOException()\r
+        {\r
+        }\r
+\r
+        public LogIOException(string message) : base(message)\r
+        {\r
+        }\r
+\r
+        public LogIOException(string message, Exception inner) : base(message, inner)\r
+        {\r
+        }\r
+    }\r
 }
\ No newline at end of file