@@ -23,6 +23,7 @@ public static class LobbyHelper {
2323 private static ILHook hookOnOuiFileSelectSlotGolden ;
2424 private static ILHook hookOnOuiJournalPoemLines ;
2525 private static ILHook hookOnLevelSetSwitch ;
26+ private static ILHook hookOnOuiFileSelectSlotRender ;
2627
2728 private static HashSet < string > collabNames = new HashSet < string > ( ) ;
2829
@@ -140,6 +141,7 @@ internal static void Load() {
140141 hookOnOuiFileSelectRenderStrawberryStamp = new ILHook ( typeof ( OuiFileSelectSlot ) . GetMethod ( "orig_Render" ) , modSelectSlotCollectedStrawberries ) ;
141142 hookOnOuiJournalPoemLines = new ILHook ( typeof ( OuiJournalPoem ) . GetNestedType ( "PoemLine" , BindingFlags . NonPublic ) . GetMethod ( "Render" ) , modJournalPoemHeartColors ) ;
142143 hookOnOuiFileSelectSlotGolden = new ILHook ( typeof ( OuiFileSelectSlot ) . GetMethod ( "get_Golden" , BindingFlags . NonPublic | BindingFlags . Instance ) , modSelectSlotCollectedStrawberries ) ;
144+ hookOnOuiFileSelectSlotRender = new ILHook ( typeof ( OuiFileSelectSlot ) . GetMethod ( "orig_Render" ) , modOuiFileSelectSlotRender ) ;
143145 }
144146
145147 internal static void Unload ( ) {
@@ -164,6 +166,7 @@ internal static void Unload() {
164166 hookOnOuiFileSelectRenderStrawberryStamp ? . Dispose ( ) ;
165167 hookOnOuiJournalPoemLines ? . Dispose ( ) ;
166168 hookOnOuiFileSelectSlotGolden ? . Dispose ( ) ;
169+ hookOnOuiFileSelectSlotRender ? . Dispose ( ) ;
167170 }
168171
169172 public static void OnSessionCreated ( ) {
@@ -483,6 +486,28 @@ private static void onOuiFileSelectSlotShow(On.Celeste.OuiFileSelectSlot.orig_Sh
483486 self . Strawberries . OutOf = maxStrawberryCount ;
484487 }
485488
489+ // figure out if some hearts are customized, and store it in DynData so that a IL hook can access it later.
490+ SaveData oldInstance = SaveData . Instance ;
491+ SaveData . Instance = self . SaveData ;
492+ List < string > customJournalHearts = new List < string > ( ) ;
493+ if ( self . SaveData != null ) {
494+ foreach ( AreaStats item in self . SaveData . Areas_Safe ) {
495+ if ( item . ID_Safe > self . SaveData . UnlockedAreas_Safe ) {
496+ break ;
497+ }
498+ if ( ! AreaData . Areas [ item . ID_Safe ] . Interlude_Safe && AreaData . Areas [ item . ID_Safe ] . CanFullClear ) {
499+ string lobbyLevelSetName = GetLobbyLevelSet ( item . GetSID ( ) ) ;
500+ if ( lobbyLevelSetName != null && MTN . Journal . Has ( "CollabUtils2Hearts/" + lobbyLevelSetName ) ) {
501+ customJournalHearts . Add ( "CollabUtils2Hearts/" + lobbyLevelSetName ) ;
502+ } else {
503+ customJournalHearts . Add ( null ) ;
504+ }
505+ }
506+ }
507+ }
508+ new DynData < OuiFileSelectSlot > ( self ) [ "collabutils2_customhearts" ] = customJournalHearts ;
509+ SaveData . Instance = oldInstance ;
510+
486511 // Restore the last area if it was replaced at the beginning of this method.
487512 if ( savedLastArea != null ) {
488513 self . SaveData . LastArea_Safe = savedLastArea . Value ;
@@ -546,6 +571,41 @@ private static void modSelectSlotCollectedStrawberries(ILContext il) {
546571 }
547572 }
548573
574+ private static void modOuiFileSelectSlotRender ( ILContext il ) {
575+ ILCursor cursor = new ILCursor ( il ) ;
576+
577+ if ( cursor . TryGotoNext ( MoveType . After ,
578+ instr => instr . MatchCall < string > ( "Concat" ) ,
579+ instr => instr . MatchCallvirt < Atlas > ( "get_Item" ) ) ) {
580+
581+ // we are after the heart was loaded for file select, and before it is rendered.
582+ // so we can intercept it and give the game another heart instead...
583+ // but we first need to know which heart it is by getting the loop index.
584+ // this loop index is used to read from the OuiFileSelectSlot.Cassettes array, so we can anchor on that
585+ ILCursor cursorLoopIndex = new ILCursor ( il ) ;
586+
587+ if ( cursorLoopIndex . TryGotoNext ( MoveType . After ,
588+ instr => instr . MatchLdfld < OuiFileSelectSlot > ( "Cassettes" ) ,
589+ instr => true ,
590+ instr => instr . OpCode == OpCodes . Callvirt && ( instr . Operand as MethodReference ) . Name == "get_Item" ) ) {
591+
592+ Instruction loopIndex = cursorLoopIndex . Prev . Previous ;
593+ Logger . Log ( "CollabUtils2/LobbyHelper" , $ "Modding heart colors on file select at { cursor . Index } in IL for { il . Method . Name } , using loop index { loopIndex } ") ;
594+
595+ cursor . Emit ( OpCodes . Ldarg_0 ) ;
596+ cursor . Emit ( loopIndex . OpCode , loopIndex . Operand ) ;
597+
598+ cursor . EmitDelegate < Func < MTexture , OuiFileSelectSlot , int , MTexture > > ( ( orig , self , index ) => {
599+ List < string > customJournalHearts = new DynData < OuiFileSelectSlot > ( self ) . Get < List < string > > ( "collabutils2_customhearts" ) ;
600+ if ( customJournalHearts != null && customJournalHearts [ index ] != null ) {
601+ return MTN . Journal [ customJournalHearts [ index ] ] ; // "Journal" and not "FileSelect" because it re-uses the setup people made for their custom journals.
602+ }
603+ return orig ;
604+ } ) ;
605+ }
606+ }
607+ }
608+
549609 private static void modJournalPoemHeartColors ( ILContext il ) {
550610 ILCursor cursor = new ILCursor ( il ) ;
551611
0 commit comments