ScoreSaver.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783
  1. #include "ScoreSaver.h"
  2. #include "GameState.h"
  3. #include "../Constants.h"
  4. #include "../framework.h"
  5. #include "../Utilities/Operations.h"
  6. #include <stdio.h>
  7. #include <windows.h>
  8. #include <detours.h>
  9. #include <algorithm>
  10. #pragma comment(lib, "detours.lib")
  11. namespace TLAC::Components
  12. {
  13. WCHAR ScoreSaver::configPath[256];
  14. WCHAR ScoreSaver::rival_configPath[256];
  15. WCHAR ScoreSaver::modules_configPath[256];
  16. WCHAR ScoreSaver::skins_configPath[256];
  17. WCHAR ScoreSaver::sfx_configPath[256];
  18. ScoreSaver::ScoreSaver()
  19. {
  20. std::string utf8path = TLAC::framework::GetModuleDirectory() + "/scores.ini";
  21. MultiByteToWideChar(CP_UTF8, 0, utf8path.c_str(), -1, configPath, 256);
  22. utf8path = TLAC::framework::GetModuleDirectory() + "/rivalscores.ini";
  23. MultiByteToWideChar(CP_UTF8, 0, utf8path.c_str(), -1, rival_configPath, 256);
  24. utf8path = TLAC::framework::GetModuleDirectory() + "/pv_equip/modules.ini";
  25. MultiByteToWideChar(CP_UTF8, 0, utf8path.c_str(), -1, modules_configPath, 256);
  26. utf8path = TLAC::framework::GetModuleDirectory() + "/pv_equip/skins.ini";
  27. MultiByteToWideChar(CP_UTF8, 0, utf8path.c_str(), -1, skins_configPath, 256);
  28. utf8path = TLAC::framework::GetModuleDirectory() + "/pv_equip/sfx.ini";
  29. MultiByteToWideChar(CP_UTF8, 0, utf8path.c_str(), -1, sfx_configPath, 256);
  30. }
  31. ScoreSaver::~ScoreSaver()
  32. {
  33. for (int diff = 0; diff < 4; diff++)
  34. {
  35. *(DivaScore**)(PLAYER_DATA_ADDRESS + diff * 0x18 + 0x5d0) = 0;
  36. *(DivaScore**)(PLAYER_DATA_ADDRESS + diff * 0x18 + 0x5d8) = 0;
  37. }
  38. }
  39. const char* ScoreSaver::GetDisplayName()
  40. {
  41. return "score_saver";
  42. }
  43. bool ScoreSaver::initCacheFinished = false;
  44. void ScoreSaver::initCache()
  45. {
  46. // build the score cache
  47. UpdateScoreCache();
  48. UpdateClearCounts();
  49. initCacheFinished = true;
  50. }
  51. bool(__stdcall* ScoreSaver::divaInitResults)(void* cls) = (bool(__stdcall*)(void* cls))RESULTS_INIT_ADDRESS;
  52. std::thread ScoreSaver::initThread;
  53. void ScoreSaver::Initialize(ComponentsManager*)
  54. {
  55. DetourTransactionBegin();
  56. DetourUpdateThread(GetCurrentThread());
  57. DetourAttach(&(PVOID&)ScoreSaver::divaInitResults, (PVOID)(ScoreSaver::hookedInitResults));
  58. DetourTransactionCommit();
  59. initThread = std::thread(initCache);
  60. }
  61. bool ScoreSaver::checkExistingScoreValid(int pv, int difficulty, int isEx)
  62. {
  63. WCHAR keyBase[32]; // needs to be big enough to store pv.999.diff.3.ex
  64. WCHAR key[32]; // needs to be big enough to store pv.999.diff.3.alltimemodifiers
  65. const WCHAR section[] = L"scores";
  66. if (isEx == 0)
  67. swprintf(keyBase, 32, L"pv.%03d.diff.%01d", pv, difficulty);
  68. else
  69. swprintf(keyBase, 32, L"pv.%03d.diff.%01d.ex", pv, difficulty);
  70. swprintf(key, 32, L"%ls.%ls", keyBase, L"score");
  71. int score = GetPrivateProfileIntW(section, key, 0, configPath);
  72. swprintf(key, 32, L"%ls.%ls", keyBase, L"notecounts");
  73. WCHAR countsBufW[32];
  74. char countsBufA[32];
  75. int cntHitTypes[2] = { 0, 0 };
  76. GetPrivateProfileStringW(section, key, L"", countsBufW, 32, configPath);
  77. WideCharToMultiByte(CP_UTF8, 0, countsBufW, -1, countsBufA, 32, NULL, NULL);
  78. std::vector<std::string> countToks = TLAC::Utilities::Split(countsBufA, ",");
  79. if (countToks.size() >= 2)
  80. {
  81. cntHitTypes[0] = atoi(countToks[0].c_str());
  82. cntHitTypes[1] = atoi(countToks[1].c_str());
  83. }
  84. swprintf(key, 32, L"%ls.%ls", keyBase, L"percent");
  85. int percent = GetPrivateProfileIntW(section, key, 0, configPath);
  86. swprintf(key, 32, L"%ls.%ls", keyBase, L"combo");
  87. int combo = GetPrivateProfileIntW(section, key, 0, configPath);
  88. swprintf(key, 32, L"%ls.%ls", keyBase, L"rank");
  89. int clearRank = GetPrivateProfileIntW(section, key, 0, configPath);
  90. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimerank");
  91. int allTimeRank = GetPrivateProfileIntW(section, key, 0, configPath);
  92. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimemodifiers");
  93. int allTimeModifiers = GetPrivateProfileIntW(section, key, 0, configPath);
  94. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimepercent");
  95. int allTimePercent = GetPrivateProfileIntW(section, key, 0, configPath);
  96. swprintf(key, 32, L"%ls.%ls", keyBase, L"check");
  97. int expectedCheck = GetPrivateProfileIntW(section, key, -1, configPath);
  98. return expectedCheck == calculateCheck(score, cntHitTypes[0], cntHitTypes[1], percent, combo, clearRank, allTimeRank, allTimeModifiers, allTimePercent);
  99. }
  100. int ScoreSaver::calculateCheck(int score, int cntCools, int cntFines, int percent, int combo, int clearRank, int allTimeRank, int allTimeModifiers, int allTimePercent)
  101. {
  102. int checkField = ((short)score ^ ((short)cntCools << 16) ^ ((short)cntFines) ^ ((short)percent << 16) ^ ((short)combo)) * clearRank;
  103. checkField += (allTimeRank * clearRank); // first extension
  104. checkField ^= (allTimeModifiers * allTimePercent);
  105. return checkField;
  106. }
  107. bool ScoreSaver::hookedInitResults(void* cls)
  108. {
  109. bool result = divaInitResults(cls);
  110. int clearRank = *(int*)(RESULTS_BASE_ADDRESS + 0xe8);
  111. byte insurance = *(byte*)(GAME_INFO_ADDRESS + 0x14);
  112. if (insurance != 0)
  113. return result;
  114. // get the base for this specific set of results
  115. uint64_t resultBase = *(uint64_t*)(RESULTS_BASE_ADDRESS + 0x100);
  116. int pvNum = *(int*)(resultBase + 0x2c);
  117. int pvDifficulty = *(int*)(resultBase + 0x34);
  118. int pvDifficultyIsEx = *(int*)(resultBase + 0x44);
  119. int modifier = *(int*)(resultBase + 0x70);
  120. int* cntHitTypes = (int*)(resultBase + 0x158);
  121. int* pctHitTypes = (int*)(resultBase + 0x16c);
  122. int combo = *(int*)(resultBase + 0x180);
  123. int challengeScore = *(int*)(resultBase + 0x184);
  124. int holdScore = *(int*)(resultBase + 0x188);
  125. int score = *(int*)(resultBase + 0x18c);
  126. int percent = *(int*)(resultBase + 0x190);
  127. int slideScore = *(int*)(resultBase + 0x194);
  128. WCHAR songName[256];
  129. //std::string utf8song = *(std::string*)(CURRENT_SONG_NAME_ADDRESS);
  130. uint64_t songnamelen = *(uint64_t*)(CURRENT_SONG_NAME_ADDRESS + 0x18);
  131. char* utf8song = songnamelen < 0x10 ? (char*)CURRENT_SONG_NAME_ADDRESS : *(char**)CURRENT_SONG_NAME_ADDRESS;
  132. MultiByteToWideChar(CP_UTF8, 0, utf8song, -1, songName, 256);
  133. WCHAR keyBase[32]; // needs to be big enough to store pv.999.diff.3.ex
  134. WCHAR key[32]; // needs to be big enough to store pv.999.diff.3.alltimemodifiers
  135. WCHAR val[32]; // needs to be big enough to store five <=four digit ints (with comma separators)
  136. const WCHAR section[] = L"scores";
  137. if (pvDifficultyIsEx == 0)
  138. swprintf(keyBase, 32, L"pv.%03d.diff.%01d", pvNum, pvDifficulty);
  139. else
  140. swprintf(keyBase, 32, L"pv.%03d.diff.%01d.ex", pvNum, pvDifficulty);
  141. swprintf(key, 32, L"%ls.%ls", keyBase, L"score");
  142. int oldScore = GetPrivateProfileIntW(section, key, 0, configPath);
  143. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimerank");
  144. int oldAlltimeRank = GetPrivateProfileIntW(section, key, -1, configPath);
  145. if (oldAlltimeRank == -1) // fallback for old scores without alltimerank
  146. {
  147. swprintf(key, 32, L"%ls.%ls", keyBase, L"rank");
  148. oldAlltimeRank = GetPrivateProfileIntW(section, key, 0, configPath);
  149. }
  150. int allTimeRank = clearRank > oldAlltimeRank ? clearRank : oldAlltimeRank;
  151. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimemodifiers");
  152. int oldAlltimeModifiers = GetPrivateProfileIntW(section, key, -1, configPath);
  153. if (oldAlltimeModifiers == -1) // fallback for old scores without alltimemodifiers
  154. {
  155. swprintf(key, 32, L"%ls.%ls", keyBase, L"modifier");
  156. oldAlltimeModifiers = GetPrivateProfileIntW(section, key, 0, configPath);
  157. oldAlltimeModifiers = oldAlltimeModifiers > 0 ? 1 << (oldAlltimeModifiers - 1) : 0;
  158. }
  159. int allTimeModifiers = oldAlltimeModifiers | ((clearRank > 1 && modifier > 0) ? (1 << (modifier - 1)) : 0); // use clearRank to not update if didn't clear
  160. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimepercent");
  161. int oldAlltimePercent = GetPrivateProfileIntW(section, key, -1, configPath);
  162. if (oldAlltimePercent == -1) // fallback for old scores without alltimepercent
  163. {
  164. swprintf(key, 32, L"%ls.%ls", keyBase, L"percent");
  165. oldAlltimePercent = GetPrivateProfileIntW(section, key, 0, configPath);
  166. }
  167. int allTimePercent = percent > oldAlltimePercent ? percent : oldAlltimePercent;
  168. bool oldScoreValid = true;
  169. if (!checkExistingScoreValid(pvNum, pvDifficulty, pvDifficultyIsEx))
  170. {
  171. oldScore = 0; // ensure new score is written
  172. allTimeRank = clearRank; // don't trust old all-time clear rank, use a new one
  173. allTimeModifiers = ((clearRank > 1 && modifier > 0) ? (1 << (modifier - 1)) : 0); // don't trust old all-time modifiers, use a new one
  174. allTimePercent = percent; // don't trust old all-time percent, use a new one
  175. oldScoreValid = false;
  176. }
  177. if (clearRank > 1 && score > oldScore)
  178. {
  179. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimemodifiers");
  180. swprintf(val, 32, L"%d", allTimeModifiers);
  181. WritePrivateProfileStringW(section, key, val, configPath);
  182. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimepercent");
  183. swprintf(val, 32, L"%d", allTimePercent);
  184. WritePrivateProfileStringW(section, key, val, configPath);
  185. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimerank");
  186. swprintf(val, 32, L"%d", allTimeRank);
  187. WritePrivateProfileStringW(section, key, val, configPath);
  188. swprintf(key, 32, L"%ls.%ls", keyBase, L"challengescore");
  189. swprintf(val, 32, L"%d", challengeScore);
  190. WritePrivateProfileStringW(section, key, val, configPath);
  191. swprintf(key, 32, L"%ls.%ls", keyBase, L"combo");
  192. swprintf(val, 32, L"%d", combo);
  193. WritePrivateProfileStringW(section, key, val, configPath);
  194. swprintf(key, 32, L"%ls.%ls", keyBase, L"holdscore");
  195. swprintf(val, 32, L"%d", holdScore);
  196. WritePrivateProfileStringW(section, key, val, configPath);
  197. swprintf(key, 32, L"%ls.%ls", keyBase, L"modifier");
  198. swprintf(val, 32, L"%d", modifier);
  199. WritePrivateProfileStringW(section, key, val, configPath);
  200. swprintf(key, 32, L"%ls.%ls", keyBase, L"name");
  201. WritePrivateProfileStringW(section, key, songName, configPath);
  202. swprintf(key, 32, L"%ls.%ls", keyBase, L"notecounts");
  203. swprintf(val, 32, L"%d,%d,%d,%d,%d", cntHitTypes[0], cntHitTypes[1], cntHitTypes[2], cntHitTypes[3], cntHitTypes[4]);
  204. WritePrivateProfileStringW(section, key, val, configPath);
  205. swprintf(key, 32, L"%ls.%ls", keyBase, L"notepercents");
  206. swprintf(val, 32, L"%d,%d,%d,%d,%d", pctHitTypes[0], pctHitTypes[1], pctHitTypes[2], pctHitTypes[3], pctHitTypes[4]);
  207. WritePrivateProfileStringW(section, key, val, configPath);
  208. swprintf(key, 32, L"%ls.%ls", keyBase, L"percent");
  209. swprintf(val, 32, L"%d", percent);
  210. WritePrivateProfileStringW(section, key, val, configPath);
  211. swprintf(key, 32, L"%ls.%ls", keyBase, L"rank");
  212. swprintf(val, 32, L"%d", clearRank);
  213. WritePrivateProfileStringW(section, key, val, configPath);
  214. swprintf(key, 32, L"%ls.%ls", keyBase, L"score");
  215. swprintf(val, 32, L"%d", score);
  216. WritePrivateProfileStringW(section, key, val, configPath);
  217. swprintf(key, 32, L"%ls.%ls", keyBase, L"slidescore");
  218. swprintf(val, 32, L"%d", slideScore);
  219. WritePrivateProfileStringW(section, key, val, configPath);
  220. int checkField = calculateCheck(score, cntHitTypes[0], cntHitTypes[1], percent, combo, clearRank, allTimeRank, allTimeModifiers, allTimePercent);
  221. swprintf(key, 32, L"%ls.%ls", keyBase, L"check");
  222. swprintf(val, 32, L"%d", checkField);
  223. WritePrivateProfileStringW(section, key, val, configPath);
  224. }
  225. else // if (oldScoreValid)
  226. {
  227. if (allTimeRank != oldAlltimeRank) // first extension
  228. {
  229. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimerank");
  230. swprintf(val, 32, L"%d", allTimeRank);
  231. WritePrivateProfileStringW(section, key, val, configPath);
  232. swprintf(key, 32, L"%ls.%ls", keyBase, L"rank");
  233. int oldClearRank = GetPrivateProfileIntW(section, key, 0, configPath);
  234. swprintf(key, 32, L"%ls.%ls", keyBase, L"check");
  235. int oldCheck = GetPrivateProfileIntW(section, key, 0, configPath);
  236. swprintf(key, 32, L"%ls.%ls", keyBase, L"check");
  237. swprintf(val, 32, L"%d", oldCheck - (oldAlltimeRank * oldClearRank) + (allTimeRank * oldClearRank));
  238. WritePrivateProfileStringW(section, key, val, configPath);
  239. }
  240. if (allTimeModifiers != oldAlltimeModifiers || allTimePercent != oldAlltimePercent) // second extension
  241. {
  242. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimemodifiers");
  243. swprintf(val, 32, L"%d", allTimeModifiers);
  244. WritePrivateProfileStringW(section, key, val, configPath);
  245. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimepercent");
  246. swprintf(val, 32, L"%d", allTimePercent);
  247. WritePrivateProfileStringW(section, key, val, configPath);
  248. swprintf(key, 32, L"%ls.%ls", keyBase, L"check");
  249. int oldCheck = GetPrivateProfileIntW(section, key, 0, configPath);
  250. swprintf(key, 32, L"%ls.%ls", keyBase, L"check");
  251. swprintf(val, 32, L"%d", oldCheck ^ (oldAlltimeModifiers * oldAlltimePercent) ^ (allTimeModifiers * allTimePercent));
  252. WritePrivateProfileStringW(section, key, val, configPath);
  253. }
  254. }
  255. UpdateSingleScoreCacheEntry(pvNum, pvDifficulty, pvDifficultyIsEx, true);
  256. if (initCacheFinished) // don't update clear counts if they're not ready yet
  257. UpdateClearCounts();
  258. if (didInitialAddressUpdate) // don't set the addresses if they're not ready yet
  259. FixScoreCacheAddresses(pvDifficulty);
  260. return result;
  261. }
  262. bool ScoreSaver::didInitialAddressUpdate = false;
  263. void ScoreSaver::Update()
  264. {
  265. // the below stuff is only verified for operating in menus
  266. if (*(GameState*)CURRENT_GAME_STATE_ADDRESS == GS_GAME && (*(SubGameState*)CURRENT_GAME_SUB_STATE_ADDRESS == SUB_SELECTOR || *(SubGameState*)CURRENT_GAME_SUB_STATE_ADDRESS == SUB_GAME_SEL))
  267. {
  268. if (!didInitialAddressUpdate)
  269. {
  270. if (initCacheFinished) // check for initThread to be done
  271. {
  272. // fix the addresses here instead of doing it unsafely in initThread
  273. FixScoreCacheAddresses(0);
  274. FixScoreCacheAddresses(1);
  275. FixScoreCacheAddresses(2);
  276. FixScoreCacheAddresses(3);
  277. didInitialAddressUpdate = true;
  278. }
  279. }
  280. else
  281. {
  282. // it probably doesn't really matter whether the initial address update is done first if I add some locks,
  283. // but I feel better knowing that there won't be multiple things racing to modify the vector
  284. // this does mean that with a lot of scores you can beat the loading into the game, thus not getting the new record banner etc. after a song....
  285. int pvNum = *(int*)SELPV_CURRENT_SONG_ADDRESS;
  286. int diff = *(int*)(GAME_INFO_ADDRESS);
  287. int diffIsEx = *(int*)(GAME_INFO_ADDRESS + 0x4);
  288. byte insurance = *(byte*)(GAME_INFO_ADDRESS + 0x14);
  289. if (pvNum != currentPv || diff != currentDifficulty || diffIsEx != currentDifficultyIsEx || insurance != currentInsurance)
  290. {
  291. DivaScore* cachedScore = GetCachedScore(pvNum, diff, diffIsEx);
  292. if (cachedScore == nullptr)
  293. {
  294. // create a score cache entry if none exists for current song
  295. ScoreCache[diff].push_back(DivaScore(pvNum, diffIsEx));
  296. // update score begin and end vars from game
  297. FixScoreCacheAddresses(diff);
  298. }
  299. else
  300. {
  301. UpdateSingleScoreCacheModulesEntry;
  302. }
  303. currentPv = pvNum;
  304. currentDifficulty = diff;
  305. currentDifficultyIsEx = diffIsEx;
  306. currentInsurance = insurance;
  307. }
  308. ModuleCheck(pvNum, diff, diffIsEx);
  309. }
  310. }
  311. }
  312. void ScoreSaver::UpdateInput()
  313. {
  314. return;
  315. }
  316. void ScoreSaver::InjectCode(void* address, const std::vector<uint8_t> data)
  317. {
  318. const size_t byteCount = data.size() * sizeof(uint8_t);
  319. DWORD oldProtect;
  320. VirtualProtect(address, byteCount, PAGE_EXECUTE_READWRITE, &oldProtect);
  321. memcpy(address, data.data(), byteCount);
  322. VirtualProtect(address, byteCount, oldProtect, nullptr);
  323. }
  324. std::vector<ScoreSaver::DivaScore> ScoreSaver::ScoreCache[4] = { // * 4 difficulties
  325. {},
  326. {},
  327. {},
  328. {}
  329. };
  330. ScoreSaver::DivaScore* ScoreSaver::GetCachedScore(int pvNum, int diff, int exDiff)
  331. {
  332. if (pvNum < 0 || diff < 0 || exDiff < 0 || pvNum > 999 || diff > 3 || exDiff > 1)
  333. return nullptr;
  334. for (DivaScore &scoreinfo : ScoreCache[diff])
  335. {
  336. if (scoreinfo.pvNum == pvNum && scoreinfo.exDifficulty == exDiff)
  337. return &scoreinfo;
  338. }
  339. return nullptr;
  340. }
  341. void ScoreSaver::UpdateSingleScoreCacheEntry(int pvNum, int diff, int exDiff, bool doDefaultsReset)
  342. {
  343. if (pvNum < 0 || diff < 0 || exDiff < 0 || pvNum > 999 || diff > 3 || exDiff > 1)
  344. return;
  345. WCHAR keyBase[32]; // needs to be big enough to store pv.999.diff.3.ex
  346. WCHAR key[32]; // needs to be big enough to store pv.999.diff.3.alltimemodifiers
  347. const WCHAR section[] = L"scores";
  348. if (exDiff == 0)
  349. swprintf(keyBase, 32, L"pv.%03d.diff.%01d", pvNum, diff);
  350. else
  351. swprintf(keyBase, 32, L"pv.%03d.diff.%01d.ex", pvNum, diff);
  352. if (checkExistingScoreValid(pvNum, diff, exDiff))
  353. {
  354. swprintf(key, 32, L"%ls.%ls", keyBase, L"score");
  355. int score = GetPrivateProfileIntW(section, key, 0, configPath);
  356. if (score > 99999999)
  357. score = 99999999;
  358. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimepercent");
  359. int percent = GetPrivateProfileIntW(section, key, -1, configPath);
  360. if (percent == -1) // fallback for old scores without alltimepercent
  361. {
  362. swprintf(key, 32, L"%ls.%ls", keyBase, L"percent");
  363. percent = GetPrivateProfileIntW(section, key, 0, configPath);
  364. }
  365. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimerank");
  366. int allTimeRank = GetPrivateProfileIntW(section, key, -1, configPath);
  367. if (allTimeRank == -1) // fallback for old scores without alltimerank
  368. {
  369. swprintf(key, 32, L"%ls.%ls", keyBase, L"rank");
  370. allTimeRank = GetPrivateProfileIntW(section, key, -1, configPath);
  371. }
  372. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimemodifiers");
  373. int modifiers = GetPrivateProfileIntW(section, key, -1, configPath);
  374. if (modifiers == -1) // fallback for old scores without alltimemodifiers
  375. {
  376. swprintf(key, 32, L"%ls.%ls", keyBase, L"modifier");
  377. modifiers = GetPrivateProfileIntW(section, key, 0, configPath);
  378. modifiers = modifiers > 0 ? 1 << (modifiers - 1) : 0;
  379. }
  380. DivaScore* cachedScore = GetCachedScore(pvNum, diff, exDiff);
  381. if (cachedScore == nullptr)
  382. {
  383. ScoreCache[diff].push_back(DivaScore(pvNum, exDiff));
  384. cachedScore = GetCachedScore(pvNum, diff, exDiff);
  385. }
  386. if (cachedScore != nullptr)
  387. {
  388. cachedScore->score = score;
  389. cachedScore->percent = percent;
  390. cachedScore->clearRank = allTimeRank;
  391. if (modifiers & 1) cachedScore->optionA = 1;
  392. if (modifiers & 2) cachedScore->optionB = 1;
  393. if (modifiers & 4) cachedScore->optionC = 1;
  394. }
  395. }
  396. else if (doDefaultsReset) // reset to defaults if not valid
  397. {
  398. DivaScore* cachedScore = GetCachedScore(pvNum, diff, exDiff);
  399. if (cachedScore != nullptr)
  400. {
  401. cachedScore->score = 0;
  402. cachedScore->percent = 0;
  403. cachedScore->clearRank = -1;
  404. cachedScore->optionA = 0;
  405. cachedScore->optionB = 0;
  406. cachedScore->optionC = 0;
  407. }
  408. }
  409. }
  410. void ScoreSaver::UpdateSingleScoreCacheRivalEntry(int pvNum, int diff, int exDiff)
  411. {
  412. if (pvNum < 0 || diff < 0 || exDiff < 0 || pvNum > 999 || diff > 3 || exDiff > 1)
  413. return;
  414. WCHAR keyBase[32]; // needs to be big enough to store pv.999.diff.3.ex
  415. WCHAR key[32]; // needs to be big enough to store pv.999.diff.3.alltimemodifiers
  416. const WCHAR section[] = L"scores";
  417. if (exDiff == 0)
  418. swprintf(keyBase, 32, L"pv.%03d.diff.%01d", pvNum, diff);
  419. else
  420. swprintf(keyBase, 32, L"pv.%03d.diff.%01d.ex", pvNum, diff);
  421. swprintf(key, 32, L"%ls.%ls", keyBase, L"score");
  422. int score = GetPrivateProfileIntW(section, key, 0, rival_configPath);
  423. if (score > 99999999)
  424. score = 99999999;
  425. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimepercent");
  426. int percent = GetPrivateProfileIntW(section, key, -1, rival_configPath);
  427. if (percent == -1) // fallback for old scores without alltimepercent
  428. {
  429. swprintf(key, 32, L"%ls.%ls", keyBase, L"percent");
  430. percent = GetPrivateProfileIntW(section, key, 0, rival_configPath);
  431. }
  432. swprintf(key, 32, L"%ls.%ls", keyBase, L"alltimerank");
  433. int allTimeRank = GetPrivateProfileIntW(section, key, -1, rival_configPath);
  434. if (allTimeRank == -1) // fallback for old scores without alltimerank
  435. {
  436. swprintf(key, 32, L"%ls.%ls", keyBase, L"rank");
  437. allTimeRank = GetPrivateProfileIntW(section, key, -1, rival_configPath);
  438. }
  439. if (score > 0 || percent > 0)
  440. {
  441. DivaScore* cachedScore = GetCachedScore(pvNum, diff, exDiff);
  442. if (cachedScore == nullptr)
  443. {
  444. ScoreCache[diff].push_back(DivaScore(pvNum, exDiff));
  445. cachedScore = GetCachedScore(pvNum, diff, exDiff);
  446. }
  447. if (cachedScore != nullptr)
  448. {
  449. cachedScore->rival_clearRank = allTimeRank;
  450. cachedScore->rival_score = score;
  451. cachedScore->rival_percent = percent;
  452. }
  453. }
  454. }
  455. void ScoreSaver::UpdateSingleScoreCacheModulesEntry(int pvNum, int diff, int exDiff)
  456. {
  457. if (pvNum < 0 || diff < 0 || exDiff < 0 || pvNum > 999 || diff > 3 || exDiff > 1)
  458. return;
  459. WCHAR keyBase[32]; // needs to be big enough to store pv.999
  460. WCHAR key[32]; // needs to be big enough to store pv.999.module5
  461. const WCHAR section[] = L"modules";
  462. swprintf(keyBase, 32, L"pv.%03d", pvNum);
  463. for (int i = 0; i < 6; ++i)
  464. {
  465. swprintf(key, 32, L"%ls.module%d", keyBase, i);
  466. int INImodule = GetPrivateProfileIntW(section, key, 0, modules_configPath);
  467. if (INImodule > 0)
  468. {
  469. DivaScore* cachedScore = GetCachedScore(pvNum, diff, exDiff);
  470. if (cachedScore == nullptr)
  471. {
  472. ScoreCache[diff].push_back(DivaScore(pvNum, exDiff));
  473. cachedScore = GetCachedScore(pvNum, diff, exDiff);
  474. }
  475. if (cachedScore != nullptr)
  476. {
  477. cachedScore->per_module_equip[i] = INImodule;
  478. }
  479. }
  480. }
  481. }
  482. void ScoreSaver::ModuleCheck(int pvNum, int diff, int exDiff)
  483. {
  484. if (pvNum < 0 || diff < 0 || exDiff < 0 || pvNum > 999 || diff > 3 || exDiff > 1)
  485. return;
  486. WCHAR keyBase[32]; // needs to be big enough to store pv.999
  487. WCHAR key[32]; // needs to be big enough to store pv.999.module5
  488. const WCHAR section[] = L"modules";
  489. swprintf(keyBase, 32, L"pv.%03d", pvNum);
  490. DivaScore* cachedScore = GetCachedScore(pvNum, diff, exDiff);
  491. for (int i = 0; i < 6; ++i)
  492. {
  493. swprintf(key, 32, L"%ls.module%d", keyBase, i);
  494. int INImodule = GetPrivateProfileIntW(section, key, 0, modules_configPath);
  495. if (INImodule != cachedScore->per_module_equip[i])
  496. {
  497. INImodule = *cachedScore->per_module_equip;
  498. WCHAR val[32];
  499. swprintf(val, 32, L"%d", cachedScore->per_module_equip[i]);
  500. WritePrivateProfileStringW(L"modules", key, val, modules_configPath);
  501. }
  502. }
  503. }
  504. void ScoreSaver::UpdateSingleScoreCacheSkinsEntry(int pvNum, int diff, int exDiff)
  505. {
  506. if (pvNum < 0 || diff < 0 || exDiff < 0 || pvNum > 999 || diff > 3 || exDiff > 1)
  507. return;
  508. WCHAR keyBase[32]; // needs to be big enough to store pv.999
  509. WCHAR key[32]; // needs to be big enough to store pv.999.module5
  510. const WCHAR section[] = L"skins";
  511. swprintf(keyBase, 32, L"pv.%03d", pvNum);
  512. swprintf(key, 32, L"%ls.skin", keyBase);
  513. int INISkin = GetPrivateProfileIntW(section, key, 0, skins_configPath);
  514. if (INISkin > -1)
  515. {
  516. DivaScore* cachedScore = GetCachedScore(pvNum, diff, exDiff);
  517. if (cachedScore == nullptr)
  518. {
  519. ScoreCache[diff].push_back(DivaScore(pvNum, exDiff));
  520. cachedScore = GetCachedScore(pvNum, diff, exDiff);
  521. }
  522. if (cachedScore != nullptr)
  523. {
  524. cachedScore->per_skin_equip = INISkin;
  525. }
  526. }
  527. }
  528. void ScoreSaver::UpdateSingleScoreCacheSFXEntry(int pvNum, int diff, int exDiff)
  529. {
  530. if (pvNum < 0 || diff < 0 || exDiff < 0 || pvNum > 999 || diff > 3 || exDiff > 1)
  531. return;
  532. WCHAR keyBase[32]; // needs to be big enough to store pv.999
  533. WCHAR key[32]; // needs to be big enough to store pv.999.module5
  534. const WCHAR section[] = L"SFX";
  535. swprintf(keyBase, 32, L"pv.%03d", pvNum);
  536. swprintf(key, 32, L"%ls.btn", keyBase);
  537. int INIBtn = GetPrivateProfileIntW(section, key, 0, sfx_configPath);
  538. if (INIBtn > 0)
  539. {
  540. DivaScore* cachedScore = GetCachedScore(pvNum, diff, exDiff);
  541. if (cachedScore == nullptr)
  542. {
  543. ScoreCache[diff].push_back(DivaScore(pvNum, exDiff));
  544. cachedScore = GetCachedScore(pvNum, diff, exDiff);
  545. }
  546. if (cachedScore != nullptr)
  547. {
  548. cachedScore->per_btn_se_equip = INIBtn;
  549. }
  550. }
  551. swprintf(keyBase, 32, L"pv.%03d", pvNum);
  552. swprintf(key, 32, L"%ls.chain", keyBase);
  553. int INIChain = GetPrivateProfileIntW(section, key, 0, sfx_configPath);
  554. if (INIChain > 0)
  555. {
  556. DivaScore* cachedScore = GetCachedScore(pvNum, diff, exDiff);
  557. if (cachedScore == nullptr)
  558. {
  559. ScoreCache[diff].push_back(DivaScore(pvNum, exDiff));
  560. cachedScore = GetCachedScore(pvNum, diff, exDiff);
  561. }
  562. if (cachedScore != nullptr)
  563. {
  564. cachedScore->per_chainslide_se_equip = INIChain;
  565. }
  566. }
  567. swprintf(keyBase, 32, L"pv.%03d", pvNum);
  568. swprintf(key, 32, L"%ls.slide", keyBase);
  569. int INISlide = GetPrivateProfileIntW(section, key, 0, sfx_configPath);
  570. if (INISlide > 0)
  571. {
  572. DivaScore* cachedScore = GetCachedScore(pvNum, diff, exDiff);
  573. if (cachedScore == nullptr)
  574. {
  575. ScoreCache[diff].push_back(DivaScore(pvNum, exDiff));
  576. cachedScore = GetCachedScore(pvNum, diff, exDiff);
  577. }
  578. if (cachedScore != nullptr)
  579. {
  580. cachedScore->per_slide_se_equip = INISlide;
  581. }
  582. }
  583. swprintf(keyBase, 32, L"pv.%03d", pvNum);
  584. swprintf(key, 32, L"%ls.touch", keyBase);
  585. int INITouch = GetPrivateProfileIntW(section, key, 0, sfx_configPath);
  586. if (INITouch > 0)
  587. {
  588. DivaScore* cachedScore = GetCachedScore(pvNum, diff, exDiff);
  589. if (cachedScore == nullptr)
  590. {
  591. ScoreCache[diff].push_back(DivaScore(pvNum, exDiff));
  592. cachedScore = GetCachedScore(pvNum, diff, exDiff);
  593. }
  594. if (cachedScore != nullptr)
  595. {
  596. cachedScore->per_slidertouch_se_equip = INITouch;
  597. }
  598. }
  599. }
  600. void ScoreSaver::FixScoreCacheAddresses(int diff)
  601. {
  602. // update score begin and end vars from game
  603. *(DivaScore**)(PLAYER_DATA_ADDRESS + diff * 0x18 + 0x5d0) = ScoreCache[diff].begin()._Ptr;
  604. *(DivaScore**)(PLAYER_DATA_ADDRESS + diff * 0x18 + 0x5d8) = ScoreCache[diff].end()._Ptr;
  605. }
  606. void ScoreSaver::UpdateScoreCache()
  607. {
  608. for (int pvNum = 0; pvNum < 1000; pvNum++)
  609. {
  610. for (int diff = 0; diff < 4; diff++)
  611. {
  612. for (int exDiff = 0; exDiff < 2; exDiff++)
  613. {
  614. UpdateSingleScoreCacheEntry(pvNum, diff, exDiff, false);
  615. UpdateSingleScoreCacheRivalEntry(pvNum, diff, exDiff);
  616. UpdateSingleScoreCacheModulesEntry(pvNum, diff, exDiff);
  617. UpdateSingleScoreCacheSkinsEntry(pvNum, diff, exDiff);
  618. UpdateSingleScoreCacheSFXEntry(pvNum, diff, exDiff);
  619. }
  620. }
  621. }
  622. }
  623. void ScoreSaver::UpdateClearCounts()
  624. {
  625. int* counts = (int*)SONG_CLEAR_COUNTS_ADDRESS;
  626. for (int i = 0; i < 20; i++)
  627. {
  628. counts[i] = 0;
  629. }
  630. for (int diff = 0; diff < 4; diff++)
  631. {
  632. for (DivaScore &scoreinfo : ScoreCache[diff])
  633. {
  634. if (scoreinfo.clearRank > 1 && scoreinfo.clearRank <= 5 && scoreinfo.exDifficulty == 0) // at least clear and no greater than perfect and not ex
  635. {
  636. counts[diff * 4 + scoreinfo.clearRank - 2] += 1;
  637. }
  638. }
  639. }
  640. // exex special case
  641. for (DivaScore &scoreinfo : ScoreCache[3])
  642. {
  643. if (scoreinfo.clearRank > 1 && scoreinfo.clearRank <= 5 && scoreinfo.exDifficulty == 1) // at least clear and no greater than perfect and IS ex
  644. {
  645. counts[4 * 4 + scoreinfo.clearRank - 2] += 1;
  646. }
  647. }
  648. // perfects count as clears for <= excellent, etc
  649. for (int diff = 0; diff < 5; diff++)
  650. {
  651. counts[diff * 4 + 2] += counts[diff * 4 + 3];
  652. counts[diff * 4 + 1] += counts[diff * 4 + 2];
  653. counts[diff * 4 + 0] += counts[diff * 4 + 1];
  654. }
  655. }
  656. }