1 // Copyright (C) 2013 Kazuhiro Fujieda <fujieda@users.sourceforge.jp>
\r
3 // This program is part of KancolleSniffer.
\r
5 // KancolleSniffer is free software: you can redistribute it and/or modify
\r
6 // it under the terms of the GNU General Public License as published by
\r
7 // the Free Software Foundation, either version 3 of the License, or
\r
8 // (at your option) any later version.
\r
10 // This program is distributed in the hope that it will be useful,
\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 // GNU General Public License for more details.
\r
15 // You should have received a copy of the GNU General Public License
\r
16 // along with this program; if not, see <http://www.gnu.org/licenses/>.
\r
19 using System.Collections.Generic;
\r
20 using System.Drawing;
\r
23 using System.Runtime.InteropServices;
\r
24 using System.Windows.Forms;
\r
26 using Codeplex.Data;
\r
29 namespace KancolleSniffer
\r
31 public partial class MainForm : Form
\r
33 private readonly Dictionary<int, string> _missions = new Dictionary<int, string>();
\r
34 private readonly string[] _missionNames = new string[3];
\r
35 private readonly RingTimer[] _missionTimers = new RingTimer[3];
\r
36 private readonly RingTimer[] _ndocTimers = new RingTimer[4];
\r
37 private readonly int[] _ndocShips = new int[4];
\r
38 private readonly RingTimer[] _kdocTimers = new RingTimer[4];
\r
39 private int _maxShips;
\r
40 private int _nowShips;
\r
41 private int _maxItems;
\r
42 private int _nowItems;
\r
43 private readonly int[] _deckShips = new int[6];
\r
44 private readonly Dictionary<int, ShipState> _shipStatuses = new Dictionary<int, ShipState>();
\r
45 private readonly Dictionary<int, string> _shipNames = new Dictionary<int, string>();
\r
46 private readonly SortedDictionary<int, QuestState> _questList = new SortedDictionary<int, QuestState>();
\r
47 private DateTime _questLastUpdated;
\r
48 private bool _slotRinged;
\r
50 private readonly string _shipNamesFile =
\r
51 Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "shipnames.json");
\r
53 private readonly string _missionsFile =
\r
54 Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "missions.json");
\r
56 private struct ShipState
\r
58 public int ShipId { get; set; }
\r
59 public int Level { get; set; }
\r
60 public int ExpToNext { get; set; }
\r
61 public int MaxHp { get; set; }
\r
62 public int NowHp { get; set; }
\r
65 private struct QuestState
\r
67 public string Name { get; set; }
\r
68 public int Progress { get; set; }
\r
73 InitializeComponent();
\r
74 FiddlerApplication.AfterSessionComplete += FiddlerApplication_AfterSessionComplete;
\r
75 for (var i = 0; i < _missionTimers.Length; i++)
\r
76 _missionTimers[i] = new RingTimer();
\r
77 for (var i = 0; i < _ndocTimers.Length; i++)
\r
78 _ndocTimers[i] = new RingTimer();
\r
79 for (var i = 0; i < _kdocTimers.Length; i++)
\r
80 _kdocTimers[i] = new RingTimer();
\r
83 private void FiddlerApplication_AfterSessionComplete(Session oSession)
\r
85 if (!oSession.bHasResponse || !oSession.uriContains("/kcsapi/api_"))
\r
87 var response = oSession.GetResponseBodyAsString();
\r
88 if (!response.StartsWith("svdata="))
\r
90 response = response.Remove(0, "svdata=".Length);
\r
91 var json = DynamicJson.Parse(response);
\r
92 if (!json.IsDefined("api_data"))
\r
94 json = json.api_data;
\r
95 if (oSession.url.EndsWith("api_get_member/ship"))
\r
97 ParseShipData(json);
\r
99 else if (oSession.uriContains("api_get_master/mission"))
\r
101 ParseMission(json);
\r
103 else if (oSession.uriContains("api_get_member/ndock"))
\r
106 Invoke(new Action(UpdateTimers));
\r
108 else if (oSession.uriContains("api_get_member/kdock"))
\r
111 Invoke(new Action(UpdateTimers));
\r
113 else if (oSession.url.Contains("api_get_member/deck"))
\r
116 Invoke(new Action(UpdateShipInfo));
\r
117 Invoke(new Action(UpdateMissionLabels));
\r
118 Invoke(new Action(UpdateTimers));
\r
120 else if (oSession.uriContains("api_get_member/basic"))
\r
122 _maxShips = (int)json.api_max_chara;
\r
123 _maxItems = (int)json.api_max_slotitem;
\r
124 Invoke(new Action(UpdateSlotCount));
\r
126 else if (oSession.uriContains("api_get_member/record"))
\r
128 _nowShips = (int)json.api_ship[0];
\r
129 _maxShips = (int)json.api_ship[1];
\r
130 _nowItems = (int)json.api_slotitem[0];
\r
131 _maxItems = (int)json.api_slotitem[1];
\r
132 Invoke(new Action(UpdateSlotCount));
\r
134 else if (oSession.uriContains("api_get_member/material"))
\r
136 foreach (var entry in json)
\r
138 if ((int)entry.api_id != 6)
\r
140 var backet = ((int)entry.api_value).ToString("D");
\r
141 Invoke(new Action<string>(text => labelNumOfBuckets.Text = text), backet);
\r
145 else if (oSession.uriContains("api_get_member/slotitem"))
\r
147 ParseSlotItem(json);
\r
148 Invoke(new Action(UpdateSlotCount));
\r
150 else if (oSession.uriContains("api_get_member/ship2"))
\r
152 ParseShipStatus(json);
\r
153 Invoke(new Action(UpdateShipInfo));
\r
155 else if (oSession.uriContains("api_get_member/ship3"))
\r
157 var deck = json.api_deck_data;
\r
158 foreach (var entry in deck)
\r
160 if ((int)entry.api_id != 1)
\r
162 for (var i = 0; i < 6; i++)
\r
163 _deckShips[i] = (int)entry.api_ship[i];
\r
165 var ship = json.api_ship_data;
\r
166 ParseShipStatus(ship);
\r
167 Invoke(new Action(UpdateShipInfo));
\r
169 else if (oSession.uriContains("api_req_sortie/battleresult"))
\r
171 if (!json.IsDefined("api_get_ship"))
\r
173 var entry = json.api_get_ship;
\r
174 _shipNames[(int)entry.api_ship_id] = (string)entry.api_ship_name;
\r
176 else if (oSession.uriContains("api_get_member/questlist"))
\r
178 ParseQuestList(json);
\r
179 Invoke(new Action(UpdateQuestList));
\r
183 private void Form1_Load(object sender, EventArgs e)
\r
186 FiddlerApplication.Startup(0, FiddlerCoreStartupFlags.RegisterAsSystemProxy);
\r
189 private void Form1_FormClosing(object sender, FormClosingEventArgs e)
\r
191 FiddlerApplication.Shutdown();
\r
195 private void timerMain_Tick(object sender, EventArgs e)
\r
200 private void ParseMission(dynamic json)
\r
202 foreach (var entry in json)
\r
203 _missions[(int)entry.api_id] = (string)entry.api_name;
\r
206 private void ParseShipData(dynamic json)
\r
208 foreach (var entry in json)
\r
209 _shipNames[(int)entry.api_ship_id] = (string)entry.api_name;
\r
212 private void LoadNames()
\r
216 ParseMission(DynamicJson.Parse(File.ReadAllText(_missionsFile)));
\r
218 catch (FileNotFoundException)
\r
223 ParseShipData(DynamicJson.Parse(File.ReadAllText(_shipNamesFile)));
\r
225 catch (FileNotFoundException)
\r
230 private void SaveNames()
\r
232 var ship = from data in _shipNames select new {api_ship_id = data.Key, api_name = data.Value};
\r
233 File.WriteAllText(_shipNamesFile, DynamicJson.Serialize(ship));
\r
235 var mission = from data in _missions select new {api_id = data.Key, api_name = data.Value};
\r
236 File.WriteAllText(_missionsFile, DynamicJson.Serialize(mission));
\r
239 private void ParseNDock(dynamic json)
\r
241 foreach (var entry in json)
\r
243 var id = (int)entry.api_id;
\r
244 _ndocTimers[id - 1].EndTime = (double)entry.api_complete_time;
\r
245 _ndocShips[id - 1] = (int)entry.api_ship_id;
\r
247 Invoke(new Action(UpdateNDocLabels));
\r
250 private void ParseKDock(dynamic json)
\r
252 foreach (var entry in json)
\r
254 var id = (int)entry.api_id;
\r
255 _kdocTimers[id - 1].EndTime = (double)entry.api_complete_time;
\r
259 private void ParseDeck(dynamic json)
\r
261 foreach (var entry in json)
\r
263 var id = (int)entry.api_id;
\r
266 Invoke((Action<string>)(text => labelFleet1.Text = text), (string)entry.api_name);
\r
267 for (var i = 0; i < 6; i++)
\r
268 _deckShips[i] = (int)entry.api_ship[i];
\r
272 var mission = entry.api_mission;
\r
273 if (mission[0] == 0)
\r
275 _missionNames[id] = "";
\r
276 _missionTimers[id].EndTime = 0;
\r
280 _missionNames[id] = _missions.TryGetValue((int)mission[1], out name) ? name : "不明";
\r
281 _missionTimers[id].EndTime = mission[2];
\r
285 private void ParseSlotItem(dynamic json)
\r
287 _nowItems = ((object[])json).Count();
\r
290 private void ParseShipStatus(dynamic json)
\r
292 _shipStatuses.Clear();
\r
293 foreach (var entry in json)
\r
295 var data = new ShipState
\r
297 ShipId = (int)entry.api_ship_id,
\r
298 Level = (int)entry.api_lv,
\r
299 ExpToNext = (int)entry.api_exp[1],
\r
300 MaxHp = (int)entry.api_maxhp,
\r
301 NowHp = (int)entry.api_nowhp
\r
303 _shipStatuses[(int)entry.api_id] = data;
\r
305 _nowShips = _shipStatuses.Count;
\r
308 private void ParseQuestList(dynamic json)
\r
310 var resetTime = DateTime.Today.AddHours(5);
\r
311 if (DateTime.Now >= resetTime && _questLastUpdated < resetTime)
\r
314 _questList.Clear();
\r
315 _questLastUpdated = DateTime.Now;
\r
317 foreach (var entry in json.api_list)
\r
319 if (entry is double)
\r
321 var id = (int)entry.api_no;
\r
322 var state = (int)entry.api_state;
\r
323 var progress = (int)entry.api_progress_flag;
\r
324 var name = (string)entry.api_title;
\r
340 _questList[id] = new QuestState {Name = name, Progress = progress};
\r
344 _questList.Remove(id);
\r
350 private void UpdateSlotCount()
\r
352 labelNumOfShips.Text = string.Format("{0:D}/{1:D}", _nowShips, _maxShips);
\r
353 if (_maxShips == 0 || // recordよりship3の方が先なので0の場合がある。
\r
354 _nowShips < _maxShips - 4)
\r
356 labelNumOfShips.ForeColor = Color.Black;
\r
357 _slotRinged = false;
\r
361 labelNumOfShips.ForeColor = Color.Red;
\r
365 _slotRinged = true;
\r
368 labelNumOfEquips.Text = string.Format("{0:D}/{1:D}", _nowItems, _maxItems);
\r
371 private void UpdateMissionLabels()
\r
373 var labels = new[] {labelMissionName1, labelMissionName2, labelMissionName3};
\r
374 for (var i = 0; i < 3; i++)
\r
375 labels[i].Text = _missionNames[i];
\r
378 private void UpdateNDocLabels()
\r
380 var ship = new[] {labelRepairShip1, labelRepairShip2, labelRepairShip3, labelRepairShip4};
\r
382 foreach (var id in _ndocShips)
\r
384 ShipState shipStatus;
\r
386 ship[i++].Text = id == 0
\r
388 : _shipStatuses.TryGetValue(id, out shipStatus) &&
\r
389 _shipNames.TryGetValue(shipStatus.ShipId, out text)
\r
395 private void UpdateShipInfo()
\r
397 var name = new[] {labelShip1, labelShip2, labelShip3, labelShip4, labelShip5, labelShip6};
\r
398 var lv = new[] {labelLv1, labelLv2, labelLv3, labelLv4, labelLv5, labelLv6};
\r
399 var hp = new[] {labelHP1, labelHP2, labelHP3, labelHP4, labelHP5, labelHP6};
\r
400 var next = new[] {labelNextLv1, labelNextLv2, labelNextLv3, labelNextLv4, labelNextLv5, labelNextLv6};
\r
402 if (_shipStatuses.Count == 0)
\r
404 for (var i = 0; i < 6; i++)
\r
406 var id = _deckShips[i];
\r
408 if (id == -1 || id == 0 || !_shipStatuses.TryGetValue(id, out info))
\r
412 hp[i].Text = "0/0";
\r
413 hp[i].BackColor = DefaultBackColor;
\r
414 next[i].Text = "0";
\r
418 name[i].Text = _shipNames.TryGetValue(info.ShipId, out text) ? text : "不明";
\r
419 lv[i].Text = info.Level.ToString("D");
\r
420 hp[i].Text = string.Format("{0:D}/{1:D}", info.NowHp, info.MaxHp);
\r
421 var damage = (double)info.NowHp / info.MaxHp;
\r
422 hp[i].BackColor = damage > 0.5 ? DefaultBackColor : damage > 0.25 ? Color.Orange : Color.Red;
\r
423 next[i].Text = info.ExpToNext.ToString("D");
\r
428 private void UpdateTimers()
\r
430 var mission = new[] {labelMission1, labelMission2, labelMission3};
\r
432 foreach (var timer in _missionTimers)
\r
435 SetTimerLabel(timer, mission[i++]);
\r
436 if (!timer.NeedRing)
\r
439 timer.NeedRing = false;
\r
441 var ndock = new[] {labelRepair1, labelRepair2, labelRepair3, labelRepair4};
\r
443 foreach (var timer in _ndocTimers)
\r
446 SetTimerLabel(timer, ndock[i++]);
\r
447 if (!timer.NeedRing)
\r
450 timer.NeedRing = false;
\r
452 var kdock = new[] {labelConstruct1, labelConstruct2, labelConstruct3, labelConstruct4};
\r
454 foreach (var timer in _kdocTimers)
\r
457 SetTimerLabel(timer, kdock[i++]);
\r
458 if (!timer.NeedRing)
\r
461 timer.NeedRing = false;
\r
465 private void SetTimerLabel(RingTimer timer, Label label)
\r
467 if (timer.NeedRing)
\r
468 label.ForeColor = Color.Red;
\r
470 label.ForeColor = Color.Black;
\r
471 label.Text = timer.ToString();
\r
474 private void UpdateQuestList()
\r
476 var name = new[] {labelQuest1, labelQuest2, labelQuest3, labelQuest4, labelQuest5};
\r
477 var progress = new[] {labelProgress1, labelProgress2, labelProgress3, labelProgress4, labelProgress5};
\r
479 foreach (var quest in _questList.Values)
\r
483 name[i].Text = quest.Name;
\r
484 progress[i++].Text = string.Format("{0:D}%", quest.Progress);
\r
489 progress[i].Text = "";
\r
493 private void Ring()
\r
495 SystemSounds.Asterisk.Play();
\r
496 var info = new FLASHWINFO();
\r
497 info.cbSize = Convert.ToUInt32(Marshal.SizeOf(info));
\r
498 info.hwnd = Handle;
\r
499 info.dwFlags = 3; // FLASHW_ALL
\r
501 info.dwTimeout = 0;
\r
502 FlashWindowEx(ref info);
\r
505 [StructLayout(LayoutKind.Sequential)]
\r
506 private struct FLASHWINFO
\r
508 public UInt32 cbSize;
\r
509 public IntPtr hwnd;
\r
510 public UInt32 dwFlags;
\r
511 public UInt32 uCount;
\r
512 public UInt32 dwTimeout;
\r
515 [DllImport("user32.dll")]
\r
516 private static extern Int32 FlashWindowEx(ref FLASHWINFO pwfi);
\r
518 private class RingTimer
\r
520 private bool _ringed;
\r
521 private DateTime _endTime;
\r
522 private TimeSpan _rest;
\r
524 public double EndTime
\r
528 // ReSharper disable once CompareOfFloatsByEqualityOperator
\r
530 _endTime = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(value / 1000);
\r
533 _endTime = DateTime.MinValue;
\r
539 public void Update()
\r
541 if (_endTime == DateTime.MinValue)
\r
543 _rest = TimeSpan.Zero;
\r
546 _rest = _endTime - DateTime.Now;
\r
547 if (_rest < TimeSpan.Zero)
\r
548 _rest = TimeSpan.Zero;
\r
549 if (_rest >= TimeSpan.FromMinutes(1) || _ringed)
\r
555 public bool NeedRing { get; set; }
\r
559 get { return _endTime != DateTime.MinValue; }
\r
562 public override string ToString()
\r
564 return _rest.Days == 0 ? _rest.ToString(@"hh\:mm\:ss") : _rest.ToString(@"d\.hh\:mm");
\r