[SPOILER="How the Unlock All Memories Code Was Made"]



This explains how the original Get All code was extended to complete every MemoryShard entry in the game.



The addresses below are for:



[CODE]

Disney Dreamlight Valley v1.23.0

Title ID: 0100D39012C1A800

Build: v3801088

[/CODE]



This post assumes that the previous Get All Clothing Items explanation has already been read.



The following parts are unchanged and will not be explained again here:



The WardrobeMenu.OnFocusIn hook
The code-cave selection
The DebugAddItem developer-check bypass
MissionManager.get_MetaClient
ItemDatabase.get_Instance
How GetAllByType creates an IItemData array
The IL2CPP array layout and loop
Reading Item from item-data object +0x18
The asynchronous behavior of DebugAddItem
Restoring the overwritten Wardrobe instruction


This post covers the additional processing required specifically for memories.



[SPOILER="1. Why the normal Get All code does not work for memories"]



The basic Get All routine can retrieve every MemoryShard item by changing the ItemType to:



[CODE]

ItemType.MemoryShard = 13

[/CODE]



A simple implementation would therefore appear to be:



[CODE]

items = database.GetAllByType(ItemType.MemoryShard)



for each itemData in items:

client.DebugAddItem(itemData.Item, 1, default)

[/CODE]



However, MemoryShard items are not ordinary unlockable list entries.



The normal DebugAddItem path does three things that prevent this simple version from completing every memory correctly:



It creates an ItemState containing one randomly selected MemoryShardIndex.
It attempts to wrap the MemoryShard into a consumable item.
AddMemoryShard normally adds only the single selected shard bit and rejects the request if that bit was already collected.


The required solution is therefore:



[CODE]

Get every MemoryShard item

↓

Keep the generated MemoryShardIndex state

↓

Prevent the MemoryShard from being wrapped as a consumable

↓

Prevent an already-collected random shard from stopping the update

↓

Replace the single-shard mask with the complete memory mask

[/CODE]



The final code combines the normal Get All loop with three function patches and an additional FULLMASK_STUB.



[/SPOILER]



[SPOILER="2. MemoryShardItemData"]



The relevant item-data class is:



[CODE]

public sealed class MemoryShardItemData :

...,

IItemData,

IItemStateProvider,

IItemConsummableOverride,

...

{

private UnknownFieldSet _unknownFields; // 0x10



```

public const int IDFieldNumber = 1;

private int iD_;                        // 0x18



...



public const int NumberOfShardsFieldNumber = 13;

private int numberOfShards_;            // 0x68



...



public int NumberOfShards { get; set; }

public int CompletedShardFlag { get; }

public Item Item { get; }

```



}

[/CODE]



The two fields used by the code are:



[CODE]

MemoryShardItemData + 0x18 = Item

MemoryShardItemData + 0x68 = NumberOfShards

[/CODE]



The main Get All loop uses ItemType 13:



[CODE]

MOV W1, #13

MOV X2, XZR

BL  ItemDatabase.GetAllByType

[/CODE]



The returned array is processed exactly as explained in the Clothing post:



[CODE]

ADD X20, X0, #0x20

LDR W21, [X0,#0x18]

MOV W22, #0



Loop:

LDR X0, [X20,X22,LSL #3]

LDR W1, [X0,#0x18]

[/CODE]



The difference is what happens after each Item is submitted to DebugAddItem.



[/SPOILER]



[SPOILER="3. How DebugAddItem creates a random MemoryShardIndex"]



`Meta.DebugAddItem.GetItemState` first derives the ItemType from the Item value.



Relevant excerpt:



[CODE]

0x7104CA9E84    LDR   W22, [X19]       // Item

...

0x7104CA9E90    LDR   X8, [X0,#0xB8]

0x7104CA9E94    LDR   W8, [X8,#4]

0x7104CA9E98    SDIV  W8, W22, W8

0x7104CA9E9C    CMP   W8, #0xD         // MemoryShard = 13

0x7104CA9EA4    CSET  W22, EQ

[/CODE]



After this:



[CODE]

W22 = 1 if the Item is a MemoryShard

W22 = 0 otherwise

[/CODE]



For a MemoryShard, the function retrieves the corresponding `MemoryShardItemData`:



[CODE]

0x7104CAA3D8    ADRP  X8,

Method$ItemDatabase.GetItemData<MemoryShardItemData>



0x7104CAA3DC    LDR   W1, [X19]        // item

0x7104CAA3E0    LDR   X2, [X8,...]     // MethodInfo

0x7104CAA3E4    BL    ItemDatabase.GetItemData<MemoryShardItemData>

[/CODE]



It reads `NumberOfShards` from `+0x68` and passes it to the game’s random-number generator:



[CODE]

0x7104CAA3F4    MOV   W1, WZR          // minimum = 0

0x7104CAA3F8    LDR   W2, [X0,#0x68]   // maximum = NumberOfShards

0x7104CAA3FC    MOV   X0, X24          // Random*

0x7104CAA400    LDP   X9, X3, [X8,#0x198]

0x7104CAA404    BLR   X9

[/CODE]



The result is in W0:



[CODE]

0 <= W0 < NumberOfShards

[/CODE]



That value is stored as the `MemoryShardIndex` of a newly created ItemState:



[CODE]

0x7104CAA40C    MOV   W1, W0

0x7104CAA410    MOV   X0, X23

0x7104CAA414    MOV   X2, XZR

0x7104CAA418    BL    ItemState.set_MemoryShardIndex



0x7104CAA41C    STR   X23, [X20]

[/CODE]



The effective logic is:



[CODE]

MemoryShardItemData data =

database.GetItemData<MemoryShardItemData>(item);



int randomIndex =

random.Next(0, data.NumberOfShards);



itemState = new ItemState();

itemState.MemoryShardIndex = randomIndex;

[/CODE]



One DebugAddItem request therefore represents one randomly selected piece of the memory.



[/SPOILER]



[SPOILER="4. How MemoryShardIndex is stored in ItemState"]



`ItemState` uses a protobuf `oneof` field:



[CODE]

public sealed class ItemState

{

public const int MealDataFieldNumber = 1;

public const int MemoryShardIndexFieldNumber = 2;

public const int ConsummableDataFieldNumber = 3;



```

...



private object state_;                        // 0x18

private ItemState.StateOneofCase stateCase_; // 0x20



public int MemoryShardIndex { get; set; }

public ItemState.StateOneofCase StateCase { get; }

```



}

[/CODE]



The oneof enum is:



[CODE]

public enum ItemState.StateOneofCase

{

None             = 0,

MealData         = 1,

MemoryShardIndex = 2,

ConsummableData  = 3,

LootPresentData  = 4,

SceneRestriction = 5,

RewardCondition  = 6

}

[/CODE]



Therefore:



[CODE]

ItemState + 0x18 = oneof payload

ItemState + 0x20 = active oneof case

[/CODE]



For a memory shard:



[CODE]

stateCase_ = MemoryShardIndex = 2

state_     = boxed integer containing the shard index

[/CODE]



`AddMemoryShard` later checks this exact case:



[CODE]

0x71054ABD60    LDR W8, [X22,#0x20]

0x71054ABD64    CMP W8, #2

0x71054ABD68    B.NE fallback_path

[/CODE]



It then reads the integer payload:



[CODE]

0x71054ABD6C    LDR X0, [X22,#0x18]

...

0x71054ABD90    LDR W8, [X0,#0x10]

[/CODE]



This is the randomly generated `MemoryShardIndex`.



[/SPOILER]



[SPOILER="5. Why the MemoryShard is converted into a consumable item"]



After creating the MemoryShard ItemState, `GetItemState` reaches:



[CODE]

0x7104CAA488    ORR W8, W22, W21

0x7104CAA48C    TBZ W8, #0, 0x7104CAA560

[/CODE]



The relevant values are:



[CODE]

W22 = isMemoryShard

W21 = tryConsummable

[/CODE]



Conceptually:



[CODE]

if (!isMemoryShard && !tryConsummable)

return;

[/CODE]



For a MemoryShard:



[CODE]

W22 = 1

[/CODE]



so the function continues into the wrapping path even when:



[CODE]

tryConsummable = false

[/CODE]



The wrapping call is:



[CODE]

0x7104CAA4B4    MOV W3, #0x1E0A6E0    // defaultConsummable

0x7104CAA4BC    MOV X0, X22            // itemToWrap

0x7104CAA4C0    MOV W1, #1             // amountToWrap

0x7104CAA4C4    MOV X2, X21            // stateToWrap

0x7104CAA4C8    MOV X4, XZR

0x7104CAA4CC    BL  ConsummableItemData.WrapIntoConsummableItem

[/CODE]



If wrapping succeeds, the original Item and ItemState outputs are replaced with the wrapped values:



[CODE]

0x7104CAA4D4    LDR X8, [X0,#0x20]

0x7104CAA4D8    MOV X21, X0

0x7104CAA4DC    STR X8, [X20]

...

0x7104CAA548    LDR W20, [X21,#0x18]

0x7104CAA55C    STR W20, [X19]

[/CODE]



This is why simply passing every MemoryShard to DebugAddItem can result in a consumable or activity item being added instead of updating memory progression.



[/SPOILER]



[SPOILER="6. Bypassing the consumable wrapper"]



The original instruction is:



[CODE]

0x7104CAA488    ORR W8, W22, W21

[/CODE]



It is replaced with:



[CODE]

0x7104CAA488    MOV W8, WZR

[/CODE]



The following instruction remains unchanged:



[CODE]

0x7104CAA48C    TBZ W8, #0, 0x7104CAA560

[/CODE]



Because W8 is now always zero, bit zero is always clear and the branch always goes to:



[CODE]

0x7104CAA560

[/CODE]



That location is the function epilogue:



[CODE]

0x7104CAA560    LDP X20, X19, [SP,...]

0x7104CAA564    LDP X22, X21, [SP,...]

...

0x7104CAA578    ADD SP, SP, #0xB0

0x7104CAA57C    RET

[/CODE]



The patch therefore keeps:



The original MemoryShard Item
The generated ItemState
The randomly selected MemoryShardIndex


but skips the later consumable wrapping stage.



This patch does not stop the ItemState from being created. It only prevents the Item and state from being replaced by a wrapped consumable.



This is a global patch to GetItemState while enabled.



It disables this wrapping path for other calls to the same function as well, so the code should only be enabled for the memory-unlock operation.



[/SPOILER]



[SPOILER="7. How Profile.AddItem routes the request to AddMemoryShard"]



After `GetItemState` returns, the generated state is passed through the normal item-addition process.



`Profile.<AddItem>g__AddItem|371_0` calculates the ItemType and uses a switch.



The MemoryShard case is case 13:



[CODE]

0x710555BD14    LDR X0, [X20,#0x28]   // ProfilePlayer*

0x710555BD18    CBZ X0, failure



0x710555BD1C    MOV X1, X25           // item

0x710555BD20    MOV X2, X19           // dispatcher

0x710555BD24    MOV X3, X24           // itemState

0x710555BD28    MOV X4, X21           // detail

0x710555BD2C    MOV X5, XZR

0x710555BD30    BL  Meta.ProfilePlayer.AddMemoryShard

[/CODE]



The generated ItemState is passed unchanged in X3.



The relevant data flow is:



[CODE]

Client.DebugAddItem

↓

DebugAddItem.GetItemState

↓

ItemState.MemoryShardIndex

↓

Profile.AddItem

↓

ItemType.MemoryShard case

↓

ProfilePlayer.AddMemoryShard(

item,

dispatcher,

itemState,

detail

)

[/CODE]



`AddMemoryShard` also validates the ItemType and state case:



[CODE]

0x71054ABCF4    LDR  X8, [X8,#0xB8]

0x71054ABCF8    LDR  W8, [X8,#4]

0x71054ABCFC    SDIV W8, W21, W8

0x71054ABD00    CMP  W8, #0xD

0x71054ABD04    B.NE return



0x71054ABD08    LDR  W8, [X22,#0x20]

0x71054ABD0C    CMP  W8, #2

0x71054ABD10    B.NE return

[/CODE]



This confirms that the function expects:



[CODE]

ItemType.MemoryShard

ItemState.StateOneofCase.MemoryShardIndex

[/CODE]



[/SPOILER]



[SPOILER="8. How memory progress is stored"]



`ProfilePlayer` contains:



[CODE]

public const int MemoryShardsFieldNumber = 39;



private readonly MapField<int, int>

memoryShards_; // 0x108

[/CODE]



The map can be represented as:



[CODE]

MemoryShard Item -> collected-shard bitmask

[/CODE]



`AddMemoryShard` reads the current mask with:



[CODE]

0x71054ABD14    LDR X0, [X23,#0x108]

...

0x71054ABD24    STUR W21, [X29,...]    // key = Item

...

0x71054ABD30    BL   MapField<int,int>.TryGetValue

[/CODE]



If the Item is not yet present, the function adds it with an initial value of zero:



[CODE]

0x71054ABD38    LDR X0, [X23,#0x108]

0x71054ABD3C    STR WZR, [SP,...]



...



0x71054ABD4C    STUR W21, [X29,...]    // key

...

0x71054ABD58    STR WZR, [SP,...]      // value = 0

0x71054ABD5C    BL  MapField<int,int>.Add

[/CODE]



Each bit represents one memory piece.



For example:



[CODE]

00000001 = piece 0 collected

00000010 = piece 1 collected

00000100 = piece 2 collected

00001000 = piece 3 collected

[/CODE]



A partially completed four-piece memory might contain:



[CODE]

00000101

[/CODE]



which means:



[CODE]

piece 0 = collected

piece 1 = missing

piece 2 = collected

piece 3 = missing

[/CODE]



[/SPOILER]



[SPOILER="9. How one MemoryShardIndex becomes one bit"]



When the ItemState contains `MemoryShardIndex`, `AddMemoryShard` reads the selected index and creates a single-bit mask:



[CODE]

0x71054ABD90    LDR W8, [X0,#0x10]

0x71054ABD94    AND W8, W8, #0x1F

0x71054ABD98    MOV W9, #1

0x71054ABD9C    LSL W24, W9, W8

[/CODE]



Equivalent logic:



[CODE]

int index = itemState.MemoryShardIndex;



uint singleShardBit =

1u << (index & 31);

[/CODE]



Examples:



[CODE]

index = 0 -> W24 = 00000001

index = 1 -> W24 = 00000010

index = 2 -> W24 = 00000100

index = 3 -> W24 = 00001000

[/CODE]



The `AND #0x1F` limits the shift amount to the lower five bits.



The actual memories use between 1 and 31 pieces, so the 32-bit mask can represent every valid memory definition.



[/SPOILER]



[SPOILER="10. Why partially collected memories can be skipped"]



After creating the single-shard bit, the game compares it with the current stored mask:



[CODE]

0x71054ABDA0    LDR W8, [SP,...]     // current mask

0x71054ABDA4    TST W24, W8

0x71054ABDA8    B.NE 0x71054ABE64

[/CODE]



`TST` performs a bitwise AND for condition flags:



[CODE]

currentMask & singleShardBit

[/CODE]



If the selected bit is already present, execution branches to:



[CODE]

0x71054ABE64    MOV W0, #6

0x71054ABE68    B   function_exit

[/CODE]



The result enum identifies value 6 as:



[CODE]

public enum AddRemoveItemResult

{

Success                     = 0,

InvalidItem                 = 1,

InventoryFull               = 2,

InvalidInventory            = 3,

NotInInventory              = 4,

InvalidCharacter            = 5,

MemoryShardAlreadyCollected = 6

}

[/CODE]



The effective logic is:



[CODE]

if ((currentMask & singleShardBit) != 0)

return MemoryShardAlreadyCollected;

[/CODE]



This is a problem for the Get All routine because the selected index is random.



A memory may still be incomplete, but if the one randomly selected piece was already collected, the entire request stops without updating that memory.



[/SPOILER]



[SPOILER="11. Disabling the already-collected checks"]



The main duplicate branch is changed from:



[CODE]

Original:

0x71054ABDA8    B.NE 0x71054ABE64



Patched:

0x71054ABDA8    NOP

[/CODE]



The preceding shard-bit calculation is not modified:



[CODE]

0x71054ABD9C    LSL W24, W9, W8

[/CODE]



The NOP only prevents the duplicate result branch from being taken.



`AddMemoryShard` also contains a fallback path used when the ItemState does not contain the expected MemoryShardIndex case.



That path uses bit zero:



[CODE]

0x71054ABE4C    MOV W8, WZR

0x71054ABE50    MOV W9, #1

0x71054ABE54    LSL W24, W9, WZR     // W24 = 1



0x71054ABE58    LDR W8, [SP,...]

0x71054ABE5C    TST W24, W8

0x71054ABE60    B.EQ 0x71054ABDAC

[/CODE]



Originally:



[CODE]

if bit zero is not already collected:

continue to the update



otherwise:

fall through to MemoryShardAlreadyCollected

[/CODE]



The conditional branch is replaced with an unconditional branch:



[CODE]

Original:

0x71054ABE60    B.EQ 0x71054ABDAC



Patched:

0x71054ABE60    B    0x71054ABDAC

[/CODE]



The normal DebugAddItem route should provide a valid MemoryShardIndex state, but both rejection paths are patched so that neither route can stop at `MemoryShardAlreadyCollected`.



These patches solve the random duplicate problem, but they do not yet complete the whole memory.



The normal code still adds only one bit.



[/SPOILER]



[SPOILER="12. Why the original ORR only adds one piece"]



After passing the duplicate check, the game retrieves the current mask:



[CODE]

0x71054ABDAC    LDR X23, [X23,#0x108]



...



0x71054ABDC8    MOV X0, X23

0x71054ABDCC    BL  MapField<int,int>.get_Item



0x71054ABDD0    LDR W8, [SP,...]     // current mask

[/CODE]



It prepares the key, value pointer and MethodInfo for `set_Item`:



[CODE]

0x71054ABDD4    ADRP X9, Method$MapField<int,int>.set_Item

0x71054ABDD8    SUB  X1, X29, ...    // key pointer

0x71054ABDDC    LDR  X3, [X9,...]    // MethodInfo

0x71054ABDE0    ADD  X2, SP, ...     // value pointer

0x71054ABDE4    MOV  X0, X23         // map

0x71054ABDE8    STUR W21, [X29,...]  // Item key

[/CODE]



The original mask update is:



[CODE]

0x71054ABDEC    ORR W8, W8, W24

[/CODE]



Equivalent logic:



[CODE]

newMask =

currentMask | singleShardBit;

[/CODE]



It then stores the new mask and updates the map:



[CODE]

0x71054ABDF0    STR W8, [SP,...]

0x71054ABDF4    BL  MapField<int,int>.set_Item

[/CODE]



Even after disabling the duplicate check, this operation would still add only one randomly selected piece.



A ten-piece memory would require up to ten separate successful requests.



The goal of this code is instead to write the complete mask in one request.



[/SPOILER]



[SPOILER="13. The game's official completion mask"]



`MemoryShardItemData` exposes:



[CODE]

public int CompletedShardFlag { get; }

[/CODE]



The native getter is:



[CODE]

0x7104398BA0    LDR W8, [X0,#0x68]    // NumberOfShards

0x7104398BA4    MOV W9, #0xFFFFFFFF

0x7104398BA8    LSL W8, W9, W8

0x7104398BAC    MVN W0, W8

0x7104398BB0    RET

[/CODE]



Equivalent formula:



[CODE]

completedShardFlag =

~(0xFFFFFFFFu << NumberOfShards);

[/CODE]



For shard counts from 1 through 31, this is also:



[CODE]

completedShardFlag =

(1u << NumberOfShards) - 1;

[/CODE]



Examples:



[CODE]

NumberOfShards = 1

Completed mask = 0x00000001

Binary         = 00000001



NumberOfShards = 3

Completed mask = 0x00000007

Binary         = 00000111



NumberOfShards = 5

Completed mask = 0x0000001F

Binary         = 00011111



NumberOfShards = 10

Completed mask = 0x000003FF

Binary         = 1111111111

[/CODE]



Every bit from zero through `NumberOfShards - 1` is set.



The actual memory definitions use `NumberOfShards` values within the supported range of 1 through 31.



[/SPOILER]



[SPOILER="14. How the game checks whether a memory is complete"]



`ProfilePlayer.IsMemoryShardCompleted` first reads the stored mask from:



[CODE]

ProfilePlayer.memoryShards_ +0x108

[/CODE]



Relevant excerpt:



[CODE]

0x71054AB85C    LDR X0, [X20,#0x108]

...

0x71054AB870    STR W19, [SP,...]     // Item key

...

0x71054AB87C    BL  MapField<int,int>.TryGetValue



0x71054AB884    LDR W20, [X29,...]    // stored mask

0x71054AB888    CBZ W20, return_false

[/CODE]



It retrieves the matching MemoryShardItemData and calculates the same full mask:



[CODE]

0x71054AB8C0    ADRP X8,

Method$ItemDatabase.GetItemData<MemoryShardItemData>



0x71054AB8C4    AND X1, X19, #0xFFFFFFFF

0x71054AB8C8    LDR X2, [X8,...]

0x71054AB8CC    BL  ItemDatabase.GetItemData<MemoryShardItemData>



0x71054AB8D4    LDR W8, [X0,#0x68]

0x71054AB8D8    MOV W9, #0xFFFFFFFF

0x71054AB8E8    LSL W8, W9, W8

0x71054AB8F4    MVN W8, W8

[/CODE]



Near the end, it compares the stored mask with that calculated mask:



[CODE]

0x71054ABA14    LDR W19, [X29,...]    // completed mask

...

0x71054ABA54    CMP W20, W19

...

0x71054ABA60    CSET W0, NE

[/CODE]



The surrounding nullable-value check makes the effective result:



[CODE]

return completedMaskExists

&& storedMask == completedMask;

[/CODE]



Writing the full completion mask therefore creates the exact state that the game itself recognizes as a completed memory.



[/SPOILER]



[SPOILER="15. Replacing the single-bit ORR with FULLMASK_STUB"]



The original instruction is:



[CODE]

0x71054ABDEC    ORR W8, W8, W24

[/CODE]



It is replaced with:



[CODE]

0x71054ABDEC    BL 0x7108CB417C

[/CODE]



This sends execution to a second code-cave routine.



At this point in `AddMemoryShard`, the following arguments have already been prepared for the upcoming `MapField.set_Item` call:



[CODE]

X0 = memoryShards_ map

X1 = pointer to Item key

X2 = pointer to mask value

X3 = MethodInfo for MapField<int,int>.set_Item

[/CODE]



The stub must call other functions to retrieve `NumberOfShards`.



Those function calls may overwrite X0 through X18, so the four prepared arguments are saved first:



[CODE]

0x7108CB417C    STP X0, X1, [SP,#-0x10]!

0x7108CB4180    STP X2, X3, [SP,#-0x10]!

[/CODE]



The stack remains 16-byte aligned.



[/SPOILER]



[SPOILER="16. Retrieving NumberOfShards inside the stub"]



`AddMemoryShard` stores its Item argument in X21 near its entry:



[CODE]

MOV X21, X1

[/CODE]



X21 is callee-saved, so it is still available when the stub is reached.



The stub obtains ItemDatabase.Instance:



[CODE]

0x7108CB4184    BL 0x7104A8A200

[/CODE]



It then passes the current MemoryShard Item in X1:



[CODE]

0x7108CB4188    MOV X1, X21

[/CODE]



and calls:



[CODE]

public IItemData GetItemData(Item item);

[/CODE]



Native call:



[CODE]

0x7108CB418C    BL 0x7104A858D0

[/CODE]



Although the declared return type is `IItemData`, this code runs inside `AddMemoryShard` after the ItemType has already been validated as MemoryShard.



The returned object is therefore a `MemoryShardItemData` instance.



The stub reads:



[CODE]

0x7108CB4190    LDR W9, [X0,#0x68]

[/CODE]



which gives:



[CODE]

W9 = MemoryShardItemData.NumberOfShards

[/CODE]



[/SPOILER]



[SPOILER="17. Calculating the full mask"]



The stub calculates:



[CODE]

0x7108CB4194    MOV W8, #-1

0x7108CB4198    MOV W10, #32

0x7108CB419C    SUB W10, W10, W9

0x7108CB41A0    LSR W8, W8, W10

[/CODE]



Equivalent formula:



[CODE]

fullMask =

0xFFFFFFFFu >>

(32 - NumberOfShards);

[/CODE]



For `NumberOfShards` values from 1 through 31, this produces the same result as the game’s `CompletedShardFlag` getter:



[CODE]

0xFFFFFFFFu >> (32 - N)



=



~(0xFFFFFFFFu << N)



=



(1u << N) - 1

[/CODE]



For example:



[CODE]

N = 5



# 0xFFFFFFFF >> 27



# 0x0000001F



00011111

[/CODE]



W8 now contains the complete memory mask instead of:



[CODE]

currentMask | singleShardBit

[/CODE]



The old mask does not need to be included because the full mask already contains every valid collected bit.



[/SPOILER]



[SPOILER="18. Returning to MapField.set_Item"]



After calculating the mask, the stub restores the prepared arguments:



[CODE]

0x7108CB41A4    LDP X2, X3, [SP],#0x10

0x7108CB41A8    LDP X0, X1, [SP],#0x10

[/CODE]



W8 is intentionally not restored.



It contains the new full mask.



The stub then branches directly back to:



[CODE]

0x71054ABDF0

[/CODE]



using:



[CODE]

0x7108CB41AC    B 0x71054ABDF0

[/CODE]



The original function continues with:



[CODE]

0x71054ABDF0    STR W8, [SP,...]

0x71054ABDF4    BL  MapField<int,int>.set_Item

[/CODE]



The result is:



[CODE]

memoryShards_[currentItem] =

completedShardFlag;

[/CODE]



The stub does not use `RET`.



The `BL` at `0x71054ABDEC` originally placed `0x71054ABDF0` in X30, but the stub’s internal BL calls overwrite X30.



That does not matter because the stub returns to the continuation point with an unconditional `B`, not through X30.



`AddMemoryShard` will later restore its own original X30 from its stack frame before returning.



[/SPOILER]



[SPOILER="19. Constructing the complete patch"]



The memory code contains five functional changes.



1. Request MemoryShard items



[CODE]

MOV W1, #13

BL  ItemDatabase.GetAllByType

[/CODE]



2. Prevent consumable wrapping



[CODE]

Original:

0x7104CAA488    ORR W8, W22, W21



Patched:

0x7104CAA488    MOV W8, WZR

[/CODE]



3. Disable the main already-collected branch



[CODE]

Original:

0x71054ABDA8    B.NE 0x71054ABE64



Patched:

0x71054ABDA8    NOP

[/CODE]



4. Disable the fallback already-collected branch



[CODE]

Original:

0x71054ABE60    B.EQ 0x71054ABDAC



Patched:

0x71054ABE60    B 0x71054ABDAC

[/CODE]



5. Replace the one-bit ORR with the full-mask stub



[CODE]

Original:

0x71054ABDEC    ORR W8, W8, W24



Patched:

0x71054ABDEC    BL 0x7108CB417C

[/CODE]



All parts serve a separate purpose.



Removing one of them changes the result:



[CODE]

Without the wrap bypass:

MemoryShard may become a consumable item.



Without the duplicate bypass:

A partially completed memory may be skipped when the random

shard is already owned.



Without the full-mask stub:

Only one shard bit is added.



Without the Get All loop:

Only a single specified memory would be processed.

[/CODE]



[/SPOILER]



[SPOILER="20. Complete assembly"]



[CODE]

// ============================================================================

// Unlock All Memories + Bitmask Logic

// Target: v1.23.0 [0100D39012C1A800][v3801088][UPD]

// Code Cave Address: 0x7108CB4118 (Main) / 0x7108CB417C (Bitmask Stub)

// ============================================================================



// --- [Block A] Hook (Mdl.Ui.WardrobeMenu$$OnFocusIn) ------------------------

// Original: MOV X0, X19

0x7107B15AD8    BL    0x7108CB4118



// --- [Block B] DebugAddItem developer-check bypass --------------------------

// Address: Meta.DebugAddItem.Types.Response$$ApplyThis

// Original: CBZ W8, loc_7104CAE46C

0x7104CAE434    NOP



// --- [Block C] GetItemState consumable-wrap bypass --------------------------

// Address: Meta.DebugAddItem$$GetItemState

// Original: ORR W8, W22, W21

0x7104CAA488    MOV   W8, WZR



// --- [Block D] Disable MemoryShardAlreadyCollected exits --------------------



// Main MemoryShardIndex path

// Original: B.NE 0x71054ABE64

//

// The preceding instruction at 0x71054ABD9C:

//     LSL W24, W9, W8

// is not modified.

0x71054ABDA8    NOP



// Fallback path

// Original: B.EQ 0x71054ABDAC

0x71054ABE60    B     0x71054ABDAC



// --- [Block E] Replace single-bit ORR with FULLMASK_STUB --------------------

// Address: Meta.ProfilePlayer$$AddMemoryShard

// Original: ORR W8, W8, W24

0x71054ABDEC    BL    0x7108CB417C



// --- [Block F] Main Get All code cave ---------------------------------------



0x7108CB4118    STP   X19, X30, [SP, #-0x10]!

0x7108CB411C    STP   X19, X20, [SP, #-0x10]!

0x7108CB4120    STP   X21, X22, [SP, #-0x10]!



// Get active Client

0x7108CB4124    BL    0x71035CC3B0

0x7108CB4128    MOV   X19, X0



// Get ItemDatabase.Instance

0x7108CB412C    BL    0x7104A8A200



// Get every MemoryShard item

0x7108CB4130    MOV   W1,  #13

0x7108CB4134    MOV   X2,  XZR

0x7108CB4138    BL    0x7104A85F80



// Prepare array loop

0x7108CB413C    ADD   X20, X0, #0x20

0x7108CB4140    LDR   W21, [X0, #0x18]

0x7108CB4144    MOV   W22, #0



// Load current MemoryShardItemData* and Item

0x7108CB4148    LDR   X0, [X20, X22, LSL #3]

0x7108CB414C    LDR   W1, [X0, #0x18]



// Client.DebugAddItem(item, 1, default)

0x7108CB4150    MOV   X0, X19

0x7108CB4154    MOV   W2, #1

0x7108CB4158    MOV   X3, XZR

0x7108CB415C    BL    0x7105B5DD30



// Loop control

0x7108CB4160    ADD   W22, W22, #1

0x7108CB4164    CMP   W22, W21

0x7108CB4168    B.LT  0x7108CB4148



// Restore original Wardrobe state

0x7108CB416C    LDP   X21, X22, [SP], #0x10

0x7108CB4170    LDP   X19, X20, [SP], #0x10

0x7108CB4174    LDP   X0,  X30, [SP], #0x10

0x7108CB4178    RET



// --- [Block G] FULLMASK_STUB -------------------------------------------------



// Preserve the arguments already prepared for MapField.set_Item

0x7108CB417C    STP   X0, X1, [SP, #-0x10]!

0x7108CB4180    STP   X2, X3, [SP, #-0x10]!



// Get ItemDatabase.Instance

0x7108CB4184    BL    0x7104A8A200



// Get MemoryShardItemData for the current Item

0x7108CB4188    MOV   X1, X21

0x7108CB418C    BL    0x7104A858D0



// Calculate the full completion mask

0x7108CB4190    LDR   W9,  [X0, #0x68]

0x7108CB4194    MOV   W8,  #-1

0x7108CB4198    MOV   W10, #32

0x7108CB419C    SUB   W10, W10, W9

0x7108CB41A0    LSR   W8,  W8, W10



// Restore MapField.set_Item arguments

0x7108CB41A4    LDP   X2, X3, [SP], #0x10

0x7108CB41A8    LDP   X0, X1, [SP], #0x10



// Continue at:

//     STR W8, [value]

//     BL  MapField<int,int>.set_Item

0x7108CB41AC    B     0x71054ABDF0

[/CODE]



[/SPOILER]



[SPOILER="21. Runtime behavior and version updates"]



The main Get All loop submits one asynchronous DebugAddItem request for every MemoryShard entry returned by ItemDatabase.



The routine does not wait for each request to finish.



The game may therefore continue processing memory updates after the code-cave loop has already returned.



Do not immediately trigger the routine again while the first set of requests is still being processed.



The routine does not check whether a memory is already complete before submitting it.



Every MemoryShard returned by GetAllByType is processed, and the corresponding stored mask is written to its complete value.



The code can complete:



Memories with no collected pieces
Partially completed memories
Memories whose randomly selected shard was already collected


The GetItemState wrap patch affects the shared function globally while enabled. Other item-addition operations should not be performed at the same time.



The two AddMemoryShard duplicate patches also change normal MemoryShard behavior while enabled.



Use the complete set of patches only for the memory-unlock operation and disable them afterward.



The following memory-specific locations must be verified after a game update:



[CODE]

Meta.DebugAddItem.GetItemState

wrap decision:

0x7104CAA488



Meta.ProfilePlayer.AddMemoryShard

main duplicate branch:

0x71054ABDA8



```

fallback duplicate branch:

0x71054ABE60



single-bit ORR:

0x71054ABDEC

```



ItemDatabase.GetItemData:

0x7104A858D0



MemoryShardItemData:

Item            +0x18

NumberOfShards  +0x68

[/CODE]



The following common locations must also be updated as described in the Clothing post:



[CODE]

WardrobeMenu.OnFocusIn

DebugAddItem developer check

MissionManager.get_MetaClient

ItemDatabase.get_Instance

ItemDatabase.GetAllByType

Client.DebugAddItem

Main code cave

FULLMASK_STUB code cave

[/CODE]



Do not update this code by applying one global address difference.



Each function, branch target, overwritten instruction and field offset must be verified in the new version.



[/SPOILER]



[/SPOILER]
