Here comes the information I've been able to make sense of
Hopefully some of it can be of use 
Could not find an index or flag saying what character was selected, kinda sucks.
Code:
[[[0x01726FE4] + 0x04] + 0x9C] This is the address of the "Root". If it is zero/0/null the client has not logged in.
"Root"+0x9C: NumberOfCharacters
"Root"+0xAC: CharacterContainer*
Code:
// SizeOf = 48 (0x30)
struct CharacterContainer {
0x004: int AllocationStart;
0x00C: int Capacity;
0x010: int ItemSize;
0x014: int ItemCount;
0x020: int FreeCount;
0x028: int AllocationEnd;
}
Not sure if it belongs in the container, but it is followed by an int that is either "0xFEEDFACE" (container empty), or "0x600DFOOD" (has been populated).
NOTE: When deleting a character, it will not be removed immediately from the container. That means ItemCount != NumberOfCharacters.
Code:
// SizeOf = 368 (0x170)
struct Character {
0x010: int Seed1;
0x0D0: int Seed2;
0x0DC: char* Name; // MaxLength = 152 (distance between the strings in memory)
0x0E4: int OnlineStatus; // 0 per default, 1: available, 2: away, 3: busy (this is per character, makes little sense)
0x0F4; int Level;
0x0F8; int ???; // 0 per default, then 1 if character has been selected but currently unselected or in character selection
0x0FC; int ParagonLevel;
0x15C; int PlayedInSeconds;
0x164; long Created; // microseconds since epoch
0x16C; long LastPlayed; // microseconds since epoch
}
Anything with a time is updated after leaving a game. The rest updates live.
Converting the timestamps for .NET:
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime utcDateTime = epoch.AddMilliseconds(microsecondsSinceEpoch / 1000);
And a take-it-or-leave-it example:
Code:
while (true)
{
int val1 = engine.Memory.Read<int>(0x01726FE4);
int val2 = engine.Memory.Read<int>(val1 + 0x04);
int val3 = engine.Memory.Read<int>(val2 + 0x9C);
Console.Clear();
Console.WriteLine("0x" + val1.ToString("X8"));
Console.WriteLine("0x" + val2.ToString("X8"));
Console.WriteLine("0x" + val3.ToString("X8") +
(val3 == 0 ? " (NULL)" : ""));
if (val3 != 0)
{
int count = engine.Memory.Read<int>(val3 + 0x9C);
int array = engine.Memory.Read<int>(val3 + 0xAC);
int allocationBeginsAt = engine.Memory.Read<int>(array + 0x004);
int capacity = engine.Memory.Read<int>(array + 0x00C);
int itemSize = engine.Memory.Read<int>(array + 0x010);
int itemCount = engine.Memory.Read<int>(array + 0x014);
int freeCount = engine.Memory.Read<int>(array + 0x020);
int allocationEndsAt = engine.Memory.Read<int>(array + 0x028);
int goodFood = engine.Memory.Read<int>(array + 0x030); // 0xFEEDFACE || 0x600DF00D
Console.WriteLine("Array: 0x" + array.ToString("X8"));
Console.WriteLine("Count: " + count);
for (int i = 0; i < itemCount; i++)
{
int item = allocationBeginsAt + i * itemSize;
int seed1 = engine.Memory.Read<int>(item + 0x010);
int seed2 = engine.Memory.Read<int>(item + 0x0D0);
int unknown1 = engine.Memory.Read<int>(item + 0x0D8);
int namePtr = engine.Memory.Read<int>(item + 0x0DC);
byte[] buffer = engine.Memory.ReadBytes(namePtr, 152);
string name = Encoding.ASCII.GetString(buffer).Split('\0')[0];
int onlinestatus = engine.Memory.Read<int>(item + 0x0E4); // 0: not defined, 1: online, 2: afk, 3: busy (character specific value)
int inactive = engine.Memory.Read<int>(item + 0x0E8); // starts off as 0, then set to 1 whenever going from selected -> unselected, possibly an update/cache flag?
int level = engine.Memory.Read<int>(item + 0x0F4);
int possibleFlag = engine.Memory.Read<int>(item + 0x0F8); // 0 for last index
int paragon = engine.Memory.Read<int>(item + 0x0FC);
int dead = engine.Memory.Read<int>(item + 0x138);
int playedSeconds = engine.Memory.Read<int>(item + 0x15C);
TimeSpan played = new TimeSpan(0, 0, playedSeconds);
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
long createdInEpochMicroseconds = engine.Memory.Read<long>(item + 0x160);
long lastPlayedInEpochMicroseconds = engine.Memory.Read<long>(item + 0x168);
DateTime created = epoch.AddMilliseconds(createdInEpochMicroseconds / 1000);
DateTime lastPlayed = epoch.AddMilliseconds(lastPlayedInEpochMicroseconds / 1000);
Console.ForegroundColor = inactive == 0 ? ConsoleColor.Red : ConsoleColor.Green;
Console.WriteLine("Character[" + i + "]: { Name: " + name + ", Seed1: " + seed1 + ", Seed2: " + seed2 + ", Inactive: " + inactive + ", Level: " + level + ", Paragon: " + paragon + " }");
Console.ResetColor();
}
}
System.Threading.Thread.Sleep(100);
}