2026-04-23 17:08:47 +02:00
|
|
|
using ModMenu;
|
|
|
|
|
using Func;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
|
|
namespace ModMenu {
|
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
|
|
public struct Gear {
|
|
|
|
|
public uint GearID;
|
|
|
|
|
public uint UnlockedSlots;
|
|
|
|
|
public uint AvailableSlots;
|
|
|
|
|
public uint Slot1;
|
|
|
|
|
public uint Slot2;
|
|
|
|
|
public uint Slot3;
|
|
|
|
|
public uint Unk1; // Default: 0x00000000
|
|
|
|
|
public uint Unk2; // Default: 0x00000000
|
|
|
|
|
public uint Unk3; // Default: 0x00000000
|
|
|
|
|
public uint TimestampLastUsed;
|
|
|
|
|
public uint HideNewIcon; // Enabled: 0x00010000 | Disabled: 0x00000000
|
|
|
|
|
public uint Unk4; // Default: 0x00000000
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class GearQueueSlot {
|
|
|
|
|
public string Class { get; set; }
|
|
|
|
|
public uint Slot { get; set; }
|
|
|
|
|
public byte[] Data { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class SaveEditor {
|
|
|
|
|
// [[0x106E975C] + 0xC] + 0x61A0 # Start of Headgear Definitions
|
|
|
|
|
// [[0x106E975C] + 0xC] + 0x31A0 # Start of Clothes Definitions
|
|
|
|
|
// [[0x106E975C] + 0xC] + 0x1A0 # Start of Shoes Definitions
|
|
|
|
|
// Each Gear class is 0x3000 big and each indivdual gear is 0x30 bytes (48 Bytes)
|
|
|
|
|
// 0x3000 / 0x30 = 0x100 Slots (256 Slots)
|
|
|
|
|
// 48 Bytes are split evenly according to struct in order above.
|
|
|
|
|
public static uint SAVEDATAMGR_ROOT_PTR = 0x101E675C; // Begin of Save Data [0x101E675C] + 0xC
|
|
|
|
|
|
|
|
|
|
public static string FetchAllGear() {
|
|
|
|
|
var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
|
|
|
|
|
uint GearSectionSize = 0x3000;
|
|
|
|
|
var results = new Dictionary<string, List<Gear>>();
|
|
|
|
|
int structSize = Marshal.SizeOf<Gear>();
|
|
|
|
|
|
|
|
|
|
var categories = new (string Name, uint Offset)[] {
|
|
|
|
|
("headgear", 0x61A0),
|
|
|
|
|
("clothing", 0x31A0),
|
|
|
|
|
("shoes", 0x1A0)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
foreach (var cat in categories) {
|
|
|
|
|
var gearList = new List<Gear>();
|
|
|
|
|
|
|
|
|
|
byte[] sectionRaw = Program.readBytes(p_save_start + cat.Offset, GearSectionSize);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < GearSectionSize; i += structSize) {
|
|
|
|
|
byte[] structBuffer = new byte[structSize];
|
|
|
|
|
Buffer.BlockCopy(sectionRaw, i, structBuffer, 0, structSize);
|
|
|
|
|
|
|
|
|
|
for (int j = 0; j < structBuffer.Length; j += 4) {
|
|
|
|
|
Array.Reverse(structBuffer, j, 4);
|
|
|
|
|
}
|
|
|
|
|
Gear item = ByteArrayToStructure<Gear>(structBuffer);
|
|
|
|
|
|
|
|
|
|
gearList.Add(item);
|
|
|
|
|
}
|
|
|
|
|
results.Add(cat.Name, gearList);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return JsonSerializer.Serialize(results, new JsonSerializerOptions {
|
|
|
|
|
WriteIndented = true,
|
|
|
|
|
IncludeFields = true
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void QueueGearSlot(uint Slot, string Class, uint GearID, uint Slot1, uint Slot2, uint Slot3) {
|
|
|
|
|
if (Slot > 255) {
|
|
|
|
|
Console.WriteLine("You can only write to Slot 0-255...");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Gear newGear = new Gear {
|
|
|
|
|
GearID = GearID,
|
|
|
|
|
UnlockedSlots = 4,
|
|
|
|
|
AvailableSlots = 4,
|
|
|
|
|
Slot1 = Slot1,
|
|
|
|
|
Slot2 = Slot2,
|
|
|
|
|
Slot3 = Slot3,
|
|
|
|
|
Unk1 = 0,
|
|
|
|
|
Unk2 = 0,
|
|
|
|
|
Unk3 = 0,
|
|
|
|
|
TimestampLastUsed = (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
|
|
|
|
HideNewIcon = 0x00010000,
|
|
|
|
|
Unk4 = 0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
byte[] rawData = StructureToByteArray(newGear);
|
|
|
|
|
for (int i = 0; i < rawData.Length; i += 4) {
|
|
|
|
|
Array.Reverse(rawData, i, 4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
httpMenu.GearQueue.Add(new GearQueueSlot { Class = Class, Slot = Slot, Data = rawData });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void WriteGearQueue() {
|
|
|
|
|
var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
|
|
|
|
|
uint start_offset = 0;
|
|
|
|
|
|
|
|
|
|
foreach (var Gear in httpMenu.GearQueue.ToList()) {
|
|
|
|
|
switch (Gear.Class.ToLower()) {
|
|
|
|
|
case "headgear": start_offset = 0x61A0; break;
|
|
|
|
|
case "clothing": start_offset = 0x31A0; break;
|
|
|
|
|
case "shoes": start_offset = 0x1A0; break;
|
|
|
|
|
default:
|
|
|
|
|
Console.WriteLine("Invalid Class selected");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Program.writeBytes((uint)(p_save_start + start_offset + (Gear.Slot * 0x30)), Gear.Data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void ChangeGender(uint gender) {
|
|
|
|
|
if (gender > 2) {
|
|
|
|
|
gender = 0;
|
|
|
|
|
}
|
|
|
|
|
var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
|
|
|
|
|
Program.writeUInt32((uint)(p_save_start + 0x190), gender);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void ChangeLevel(uint level) {
|
|
|
|
|
uint ActualLevel = level - 1;
|
|
|
|
|
var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
|
|
|
|
|
Program.writeUInt32((uint)(p_save_start + 0xA5A8), ActualLevel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void ChangeCash(uint amount) {
|
|
|
|
|
if (amount > 9999999) {
|
|
|
|
|
amount = 9999999;
|
|
|
|
|
}
|
|
|
|
|
var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
|
|
|
|
|
Program.writeUInt32((uint)(p_save_start + 0xA5A0), amount);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void ChangeSeaSnails(uint amount) {
|
|
|
|
|
if (amount > 999) {
|
|
|
|
|
amount = 999;
|
|
|
|
|
}
|
|
|
|
|
var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
|
|
|
|
|
Program.writeUInt32((uint)(p_save_start + 0xA5B4), amount);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-27 08:36:30 +02:00
|
|
|
public static void ChangeEquippedWeapon(uint WeaponSetID) {
|
|
|
|
|
var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
|
|
|
|
|
Program.writeUInt32((uint)(p_save_start + 0x18C), WeaponSetID);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 17:08:47 +02:00
|
|
|
private static T ByteArrayToStructure<T>(byte[] bytes) where T : struct {
|
|
|
|
|
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
|
|
|
|
|
try {
|
|
|
|
|
return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
|
|
|
|
|
} finally {
|
|
|
|
|
handle.Free();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static byte[] StructureToByteArray(object obj) {
|
|
|
|
|
int len = Marshal.SizeOf(obj);
|
|
|
|
|
byte[] arr = new byte[len];
|
|
|
|
|
IntPtr ptr = Marshal.AllocHGlobal(len);
|
|
|
|
|
try {
|
|
|
|
|
Marshal.StructureToPtr(obj, ptr, true);
|
|
|
|
|
Marshal.Copy(ptr, arr, 0, len);
|
|
|
|
|
} finally {
|
|
|
|
|
Marshal.FreeHGlobal(ptr);
|
|
|
|
|
}
|
|
|
|
|
return arr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|