1 // Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>
\r
3 // Licensed under the Apache License, Version 2.0 (the "License");
\r
4 // you may not use this file except in compliance with the License.
\r
5 // You may obtain a copy of the License at
\r
7 // http://www.apache.org/licenses/LICENSE-2.0
\r
9 // Unless required by applicable law or agreed to in writing, software
\r
10 // distributed under the License is distributed on an "AS IS" BASIS,
\r
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
12 // See the License for the specific language governing permissions and
\r
13 // limitations under the License.
\r
16 using System.Collections.Generic;
\r
17 using System.Globalization;
\r
20 using System.Net.Sockets;
\r
22 using System.Text.RegularExpressions;
\r
24 namespace KancolleSniffer
\r
26 public class LogServer
\r
28 private static readonly string IndexDir = AppDomain.CurrentDomain.BaseDirectory;
\r
29 private static string _outputDir = AppDomain.CurrentDomain.BaseDirectory;
\r
31 public static string OutputDir
\r
33 set => _outputDir = value;
\r
36 public static MaterialCount[] MaterialHistory { private get; set; }
\r
38 public static void Process(Socket client, string requestLine)
\r
40 var from = DateTime.MinValue;
\r
41 var to = DateTime.MaxValue;
\r
42 var timestamp = false;
\r
44 var request = requestLine.Split(' ');
\r
45 if (request.Length != 3)
\r
47 SendError(client, "400 Bad Request");
\r
50 if (!request[0].StartsWith("GET", StringComparison.OrdinalIgnoreCase))
\r
52 SendError(client, "501 Not Implemented");
\r
55 var tmp = request[1].Split('?');
\r
56 var path = HttpUtility.UrlDecode(tmp[0]);
\r
57 if (path == null || !path.StartsWith("/"))
\r
59 SendError(client, "400 Bad Request");
\r
62 if (tmp.Length == 2)
\r
64 var query = HttpUtility.ParseQueryString(tmp[1]);
\r
65 if (query["from"] != null)
\r
67 double.TryParse(query["from"], out var tick);
\r
68 from = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(tick / 1000);
\r
70 if (query["to"] != null)
\r
72 double.TryParse(query["to"], out var tick);
\r
73 to = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(tick / 1000);
\r
75 if (query["number"] != null)
\r
76 timestamp = query["number"] == "true";
\r
79 path = path == "/" ? "index.html" : path.Substring(1);
\r
80 var full = Path.Combine(IndexDir, path);
\r
81 var csv = Path.Combine(_outputDir, path);
\r
82 if (path.EndsWith(".html", StringComparison.OrdinalIgnoreCase) && File.Exists(full))
\r
84 SendFile(client, full, "text/html");
\r
87 if (path.EndsWith(".csv", StringComparison.OrdinalIgnoreCase) && File.Exists(csv))
\r
89 SendFile(client, csv, "text/csv; charset=Shift_JIS");
\r
92 if (path.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
\r
94 SendJsonData(client, csv, from, to, timestamp);
\r
97 if (path.EndsWith(".js", StringComparison.OrdinalIgnoreCase) && File.Exists(full))
\r
99 SendFile(client, full, "application/javascript");
\r
102 if (path.EndsWith(".pac"))
\r
104 SendProxyPac(client, HttpProxy.LocalPort);
\r
107 SendError(client, "404 Not Found");
\r
110 private static void SendError(Socket client, string error)
\r
112 using (var writer = new StreamWriter(new MemoryStream(), Encoding.ASCII))
\r
114 writer.Write("HTTP/1.1 {0}\r\n", error);
\r
115 writer.Write("Server: KancolleSniffer\r\n");
\r
116 writer.Write("Date: {0:R}\r\n", DateTime.Now);
\r
117 writer.Write("Connection: close\r\n\r\n");
\r
118 writer.Write("<html><head><title>{0}</title></head>\r\n", error);
\r
119 writer.Write("<body><h4>{0}</h4></body></html>\r\n\r\n", error);
\r
121 client.Send(((MemoryStream)writer.BaseStream).ToArray());
\r
125 private static void SendJsonData(Socket client, string path, DateTime from, DateTime to, bool number)
\r
127 using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))
\r
129 header.Write("HTTP/1.1 200 OK\r\n");
\r
130 header.Write("Server: KancolleSniffer\r\n");
\r
131 header.Write("Date: {0:R}\r\n", DateTime.Now);
\r
132 header.Write("Content-Type: {0}\r\n", "application/json; charset=Shift_JIS");
\r
133 header.Write("Connection: close\r\n\r\n");
\r
135 client.Send(((MemoryStream)header.BaseStream).ToArray());
\r
137 var csv = path.Replace(".json", ".csv");
\r
138 var encoding = Encoding.GetEncoding("Shift_JIS");
\r
139 client.Send(encoding.GetBytes("{ \"data\": [\n"));
\r
140 var battle = false;
\r
141 var material = false;
\r
144 if (!File.Exists(csv))
\r
147 if (path.EndsWith("遠征報告書.json"))
\r
151 else if (path.EndsWith("改修報告書.json"))
\r
155 else if (path.EndsWith("海戦・ドロップ報告書.json"))
\r
160 else if (path.EndsWith("開発報告書.json"))
\r
164 else if (path.EndsWith("建造報告書.json"))
\r
168 else if (path.EndsWith("資材ログ.json"))
\r
173 else if (path.EndsWith("戦果.json"))
\r
177 var delimiter = "";
\r
178 foreach (var line in File.ReadLines(csv, encoding).Skip(1))
\r
180 var data = line.Split(',');
\r
181 if (!DateTime.TryParseExact(data[0], Logger.DateTimeFormat, CultureInfo.InvariantCulture,
\r
182 DateTimeStyles.AssumeLocal, out DateTime date))
\r
184 // システムが和暦に設定されていて和暦が出力されてしまったケースを救う
\r
185 var wareki = CultureInfo.CreateSpecificCulture("ja-JP");
\r
186 wareki.DateTimeFormat.Calendar = new JapaneseCalendar();
\r
187 if (DateTime.TryParseExact(data[0], Logger.DateTimeFormat, wareki,
\r
188 DateTimeStyles.AssumeLocal, out date))
\r
190 data[0] = Logger.FormatDateTime(date);
\r
192 else if (DateTime.TryParse(data[0], CultureInfo.CurrentCulture,
\r
193 DateTimeStyles.AssumeLocal, out date))
\r
195 data[0] = Logger.FormatDateTime(date);
\r
202 if (date < from || to < date)
\r
204 IEnumerable<string> entries = data;
\r
206 entries = data.Take(9);
\r
208 entries = BattleLogProcessor.Process(data);
\r
209 if (entries.Count() != records)
\r
213 var stamp = ((date.ToUniversalTime().Ticks -
\r
214 new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks) /
\r
215 TimeSpan.TicksPerMillisecond).ToString();
\r
216 client.Send(encoding.GetBytes(delimiter + "[" + stamp + "," +
\r
217 string.Join(",", entries.Skip(1)) + "]"));
\r
221 client.Send(encoding.GetBytes(delimiter + "[\"" +
\r
222 string.Join("\",\"", entries) + "\"]"));
\r
226 if (material && !number)
\r
228 client.Send(encoding.GetBytes(delimiter + "[\"" +
\r
229 string.Join("\",\"", GetCurrentMaterialRecord()) + "\"]"));
\r
234 client.Send(encoding.GetBytes("]}\n"));
\r
238 private static IEnumerable<string> GetCurrentMaterialRecord()
\r
240 return new[] {Logger.FormatDateTime(DateTime.Now)}.
\r
241 Concat(MaterialHistory.Select(c => c.Now.ToString()));
\r
244 private static void SendFile(Socket client, string path, string mime)
\r
246 using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))
\r
248 header.Write("HTTP/1.1 200 OK\r\n");
\r
249 header.Write("Server: KancolleSniffer\r\n");
\r
250 header.Write("Date: {0:R}\r\n", DateTime.Now);
\r
251 header.Write("Content-Length: {0}\r\n", new FileInfo(path).Length);
\r
252 header.Write("Content-Type: {0}\r\n", mime);
\r
253 header.Write("Connection: close\r\n\r\n");
\r
255 client.SendFile(path, ((MemoryStream)header.BaseStream).ToArray(), null,
\r
256 TransmitFileOptions.UseDefaultWorkerThread);
\r
260 private static void SendProxyPac(Socket client, int port)
\r
262 using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))
\r
264 header.Write("HTTP/1.1 200 OK\r\n");
\r
265 header.Write("Server: KancolleSniffer\r\n");
\r
266 header.Write("Date: {0:R}\r\n", DateTime.Now);
\r
267 header.Write("Content-Type: application/x-ns-proxy-autoconfig\r\n");
\r
268 header.Write("Connection: close\r\n\r\n");
\r
270 client.Send(((MemoryStream)header.BaseStream).ToArray());
\r
275 pacFile = File.ReadAllText("proxy.pac").Replace("8080", port.ToString());
\r
281 client.Send(Encoding.ASCII.GetBytes(pacFile));
\r
285 public static class BattleLogProcessor
\r
287 public static IEnumerable<string> Process(string[] data)
\r
289 if (data.Length == 35)
\r
290 data = data.Concat(Enumerable.Repeat("", 3)).ToArray();
\r
291 if (data.Length == 40)
\r
293 data = data.Take(21).Concat(new[] {data[21] + "・" + data[23], data[22] + "・" + data[24]})
\r
294 .Concat(data.Skip(25)).ToArray();
\r
296 else if (data.Length != 38)
\r
300 if (data[5] == "T字戦(有利)")
\r
302 if (data[5] == "T字戦(不利)")
\r
304 if (data[6].EndsWith("航行序列"))
\r
305 data[6] = data[6].Substring(0, 4);
\r
306 if (data[7].EndsWith("航行序列"))
\r
307 data[7] = data[7].Substring(0, 4);
\r
308 data[37] = ShortenAirBattleResult(data[37]);
\r
309 return AddDamagedShip(data);
\r
312 private static string ShortenAirBattleResult(string result)
\r
331 private static IEnumerable<string> AddDamagedShip(string[] data)
\r
333 var damaged = new List<string>();
\r
334 for (var i = 11; i < 11 + 12; i += 2)
\r
338 var ship = data[i] = StripKana(data[i]);
\r
339 var hp = data[i + 1];
\r
342 if (ship.Contains("・"))
\r
344 var ships = ship.Split('・');
\r
345 var hps = hp.Split('・');
\r
346 var nowMax = hps[0].Split('/').Select(int.Parse).ToArray();
\r
347 if (ShipStatus.CalcDamage(nowMax[0], nowMax[1]) == ShipStatus.Damage.Badly)
\r
348 damaged.Add(ships[0]);
\r
349 nowMax = hps[1].Split('/').Select(int.Parse).ToArray();
\r
350 if (ShipStatus.CalcDamage(nowMax[0], nowMax[1]) == ShipStatus.Damage.Badly)
\r
351 damaged.Add(ships[1]);
\r
355 var nowMax = hp.Split('/').Select(int.Parse).ToArray();
\r
356 if (ShipStatus.CalcDamage(nowMax[0], nowMax[1]) == ShipStatus.Damage.Badly)
\r
360 catch (FormatException)
\r
365 return data.Take(23).Concat(new[] { string.Join("・", damaged) }).Concat(data.Skip(23));
\r
368 private static readonly Regex Kana = new Regex(@"\([^)]+\)\(", RegexOptions.Compiled);
\r
370 private static string StripKana(string name)
\r
372 return Kana.Replace(name, "(");
\r