/// <summary> /// Prints latest play data to the screen /// </summary> /// <param name="latestData"></param> static void Print_PlayData(PlayData latestData) { Console.WriteLine("\nLATEST CLEAR:"); var header = Config.GetTsvHeader(); var entry = latestData.GetTsvEntry(); var h = header.Split('\t'); var e = entry.Split('\t'); for (int i = 0; i < h.Length; i++) { Console.WriteLine("{0,15}: {1,-50}", h[i], e[i]); } }
/// <summary> /// Send play information to remote /// </summary> /// <param name="latestData">Play information</param> public static async void SendPlayData(PlayData latestData) { var content = new FormUrlEncodedContent(latestData.ToPostForm()); try { Console.WriteLine($"Saving to server {Config.Server}"); var response = await client.PostAsync(Config.Server + "/api/songplayed", content); var responseString = await response.Content.ReadAsStringAsync(); Console.WriteLine(responseString); } catch { Console.WriteLine("Uploading failed"); } }
static void Main() { Utils.CheckVersion(); Config.Parse("config.ini"); #region Parse unlockdb file Dictionary <string, Tuple <int, int> > unlock_db = new Dictionary <string, Tuple <int, int> >(); bool init = false; if (Config.Save_remote) { if (File.Exists("unlockdb")) { foreach (var line in File.ReadAllLines("unlockdb")) { var s = line.Split(','); unlock_db.Add(s[0], new Tuple <int, int>(int.Parse(s[1]), int.Parse(s[2]))); } } else { init = true; Console.WriteLine("unlockdb not found, will initialize all songs on remote server"); } } #endregion #region Set up session and json file naming DateTime now = DateTime.Now; CultureInfo culture = CultureInfo.CreateSpecificCulture("en-us"); DateTimeFormatInfo dtformat = culture.DateTimeFormat; dtformat.TimeSeparator = "_"; DirectoryInfo sessionDir = new DirectoryInfo("sessions"); if (Config.Save_local) { if (!sessionDir.Exists) { sessionDir.Create(); } } var sessionFile = new FileInfo(Path.Combine(sessionDir.FullName, $"Session_{now:yyyy_MM_dd_hh_mm_ss}.tsv")); var jsonfile = new FileInfo(Path.Combine(sessionDir.FullName, $"Session_{now:yyyy_MM_dd_hh_mm_ss}.json")); #endregion #region Repeadedly attempt hooking to application do { Process process = null; Console.WriteLine("Trying to hook to INFINITAS..."); do { var processes = Process.GetProcessesByName("bm2dx"); if (processes.Any()) { process = processes[0]; } Thread.Sleep(2000); } while (process == null); Console.Clear(); Console.WriteLine("Hooked to process, waiting until song list is loaded..."); IntPtr processHandle = OpenProcess(0x0410, false, process.Id); /* Open process for memory read */ Utils.handle = processHandle; Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var bm2dxModule = process.MainModule; Offsets.LoadOffsets("offsets.txt"); /* Figure out if offset file is relevant */ #region Figure out if offset file is relevant Utils.Debug($"Baseaddr is {bm2dxModule.BaseAddress.ToString("X")}"); byte[] buffer = new byte[80000000]; /* 80MB */ int nRead = 0; ReadProcessMemory((int)processHandle, (long)bm2dxModule.BaseAddress, buffer, buffer.Length, ref nRead); string versionSearch = "P2D:J:B:A:"; var str = Encoding.GetEncoding("Shift-JIS").GetString(buffer); bool correctVersion = false; string foundVersion = ""; for (int i = 0; i < str.Length - Offsets.Version.Length; i++) { if (str.Substring(i, versionSearch.Length) == versionSearch) { foundVersion = str.Substring(i, Offsets.Version.Length); Utils.Debug($"Found version {foundVersion} at address +0x{i:X}"); /* Don't break, first two versions appearing are referring to 2016-builds, actual version appears later */ } } if (foundVersion != Offsets.Version) { if (Config.UpdateFiles) { Console.WriteLine($"Version in binary ({foundVersion}) don't match offset file ({Offsets.Version}), querying server for correct version"); correctVersion = Network.UpdateOffset(foundVersion); } else { Console.WriteLine($"Version in binary ({foundVersion}) don't match offset file ({Offsets.Version})"); } } else { correctVersion = true; } Network.UpdateSupportFile("encodingfixes"); Network.UpdateSupportFile("customtypes"); Utils.LoadEncodingFixes(); Utils.LoadCustomTypes(); if (!correctVersion) { Console.WriteLine("Application will now exit"); Console.ReadLine(); return; } #endregion #region Wait until data is properly loaded bool songlistFetched = false; bool processExit = false; while (!songlistFetched) { /* Don't fetch song list until it seems loaded */ while (!Utils.DataLoaded()) { Thread.Sleep(5000); if (process.HasExited) { processExit = true; break; } } if (process.HasExited || processExit) { processExit = true; break; } Thread.Sleep(1000); /* Extra sleep just to avoid potentially undiscovered race conditions */ Utils.FetchSongDataBase(); if (Utils.songDb["80003"].totalNotes[3] < 10) /* If Clione (Ryu* Remix) SPH has less than 10 notes, the songlist probably wasn't completely populated when we fetched it. That memory space generally tends to hold 0, 2 or 3, depending on which 'difficulty'-doubleword you're reading */ { Utils.Debug("Notecount data seems bad, retrying fetching in case list wasn't fully populated."); Thread.Sleep(5000); } else { songlistFetched = true; } } if (processExit) { continue; } #endregion Console.WriteLine("Song list loaded."); #region Create session and json file if configured to track those if (Config.Save_local) { File.Create(sessionFile.FullName).Close(); WriteLine(sessionFile, Config.GetTsvHeader()); } if (Config.Save_json) { JObject head = new JObject(); head["service"] = "Infinitas"; head["game"] = "iidx"; JObject json = new JObject(); json["head"] = head; json["body"] = new JArray(); File.WriteAllText(jsonfile.FullName, json.ToString()); } #endregion /* Primarily for debugging and checking for encoding issues */ if (Config.Output_songlist) { List <string> p = new List <string>() { "id\ttitle\ttitle2\tartist\tgenre" }; foreach (var v in Utils.songDb) { p.Add($"{v.Key}\t{v.Value.title}\t{v.Value.title_english}\t{v.Value.artist}\t{v.Value.genre}"); } File.WriteAllLines("songs.tsv", p.ToArray()); } Utils.GetUnlockStates(); Console.WriteLine("Fetching song scoring data..."); ScoreMap.LoadMap(); Tracker.LoadTracker(); #region Sync with server if (Config.Save_remote) { Console.WriteLine("Checking for songs/charts to update at remote."); int songcount = Utils.songDb.Where(song => !unlock_db.ContainsKey(song.Key)).Count(); Console.WriteLine($"Found {songcount} songs to upload to remote"); int i = 0; foreach (var song in Utils.songDb) { if (songcount > 0) { double percent = ((double)i) / songcount * 100; Console.Write($"\rProgress: {percent.ToString("0.##")}% "); } /* Upload new songs */ if (!unlock_db.ContainsKey(song.Key) || init) { Network.UploadSongInfo(song.Key); } /* Upload changes to song (unlock type and unlock status) */ if (unlock_db.ContainsKey(song.Key)) { if (unlock_db[song.Key].Item1 != (int)Utils.unlockDb[song.Key].type) { Network.UpdateChartUnlockType(song.Value); } if (unlock_db[song.Key].Item2 != Utils.unlockDb[song.Key].unlocks) { Network.ReportUnlock(song.Key, Utils.unlockDb[song.Key]); } } i++; } Console.WriteLine("\rDone "); } #endregion GameState state = GameState.songSelect; Console.WriteLine("Initialized and ready"); string chart = ""; if (Config.Stream_Marquee) { Utils.Debug("Updating marquee.txt"); File.WriteAllText("marquee.txt", Config.MarqueeIdleText); } if (Config.Stream_Playstate) { Utils.Debug("Writing initial menu state to playstate.txt"); File.WriteAllText("playstate.txt", "menu"); } /* Main loop */ while (!process.HasExited) { try { var newstate = Utils.FetchGameState(state); Utils.Debug(newstate.ToString()); if (newstate != state) { Console.Clear(); Console.WriteLine($"STATUS:{(newstate != GameState.playing ? " NOT" : "")} PLAYING"); if (newstate == GameState.resultScreen) { Thread.Sleep(1000); /* Sleep to avoid race condition */ var latestData = new PlayData(); latestData.Fetch(); if (latestData.DataAvailable) { if (Config.Save_remote) { Network.SendPlayData(latestData); } if (Config.Save_local) { try { /* Update best lamp/grade */ Chart c = new Chart() { songID = latestData.Chart.songid, difficulty = latestData.Chart.difficulty }; var entry = Tracker.trackerDb[c]; entry.grade = (Grade)Math.Max((int)entry.grade, (int)latestData.Grade); entry.lamp = (Lamp)Math.Max((int)entry.lamp, (int)latestData.Lamp); entry.misscount = Math.Min(entry.misscount, latestData.MissCount); entry.ex_score = Math.Max(entry.ex_score, latestData.ExScore); Tracker.trackerDb[c] = entry; Tracker.SaveTracker(); WriteLine(sessionFile, latestData.GetTsvEntry()); } catch (Exception e) { Utils.Except(e, "SessionEntry"); } } if (Config.Save_json) { var entry = latestData.GetJsonEntry(); var json = JsonConvert.DeserializeObject <JObject>(File.ReadAllText(jsonfile.FullName)); JArray arr = (JArray)json["body"]; arr.Add(entry); json["body"] = arr; File.WriteAllText(jsonfile.FullName, json.ToString()); } } try { Print_PlayData(latestData); } catch (Exception e) { Utils.Except(e, "ConsoleOutput"); } if (Config.Stream_Playstate) { Utils.Debug("Writing menu state to playstate.txt"); File.WriteAllText("playstate.txt", "menu"); } if (Config.Stream_Marquee) { Utils.Debug("Updating marquee.txt"); var clearstatus = latestData.Lamp == Lamp.F ? "FAIL!" : "CLEAR!"; File.WriteAllText("marquee.txt", $"{chart} {clearstatus}"); } } else if (newstate == GameState.songSelect && Config.Stream_Marquee) { Utils.Debug("Updating marquee.txt"); File.WriteAllText("marquee.txt", Config.MarqueeIdleText); } else { if (Config.Stream_Playstate) { Utils.Debug("Writing play state to playstate.txt"); File.WriteAllText("playstate.txt", "play"); } if (Config.Stream_Marquee) { chart = Utils.CurrentChart(); Utils.Debug($"Writing {chart} to marquee.txt"); File.WriteAllText("marquee.txt", chart.ToUpper()); } } } state = newstate; if (state == GameState.songSelect) { var newUnlocks = Utils.UpdateUnlockStates(); if (Config.Save_local) { Utils.Debug("Saving tracker data tsv"); Tracker.SaveTrackerData("tracker.tsv"); } if (Config.Save_remote && newUnlocks.Count > 0) { Network.ReportUnlocks(newUnlocks); } } Thread.Sleep(2000); } catch (Exception e) { Utils.Except(e, "MainLoop"); } } if (Config.Save_local) { Tracker.SaveTrackerData("tracker.tsv"); } if (Config.Stream_Playstate) { Utils.Debug("Writing menu state to playstate.txt"); File.WriteAllText("playstate.txt", "off"); } if (Config.Stream_Marquee) { Utils.Debug($"Writing NO SIGNAL to marquee.txt"); File.WriteAllText("marquee.txt", "NO SIGNAL"); } } while (true); #endregion }