Ok, so here is a write up on the process I go through every patchday:
And just to be clear, much of this is automated already, it still takes time.
Really at this point, the only thing you can improve is the automation itself, there isn't much manual work left to automate, but Ill explain:
The initial step I guess could be automated as well, but for practical/lazy reasons I just do these manually:
1. I fire up IDA (10 sec)
2. Load in the new exe. (1 sec)
3. wait for it to finish analyzing, (on my laptop this can take 5-15 min depending on load/other things running.)
4. At this point I dump the xrefs with a super secret IDAscript (patchday.idc). (3-5 min)
5. I run my "eqoffsetextractor.exe" (just basically what patchday.exe does but with a couple personal modifications) on this dump comparing it to previous exe xref dump which outputs the new offsets. (5-10 min)
6. I run my structchecker.exe which basically tells me what structs has changed/their new sizes and a few offsets where they are located. (5 min)
7. I now have a list of stuff I need to do, i.e what structs has changed and needs correcting.
At this point some manual inspection is needed, due to the fact that patches ARE complicated, and I also believe SOE, has implemented opcode/struct regeneration code just to move struct members around on purpose, to delay MQ2 release after patches, so its not just "fighting" a compiler that always does stuff in a logical way, you are actually up against someone that ACTIVLY and intentionally code to obscure and delay you from realigning structs.
Let just get this out of the way, the offsets, are easy to deal with, THOOSE are 99% automated already, the time consuming stuff to deal with are the structures. So here goes:
8. I need to go to the addresses my checker has output, so for this exercise, lets just say the only struct that was changed was SpawnInfo
and BTW in 9 out of 10 patches SpawnInfo WILL change, I see a patch once a year or so, where its not changed, and on those rare occacions
the MQ2 release can be done after some testing in game (15-60 min) So unless you want to leave ALL the testing to the users, and spend the next day or so listening to complaints about some offset being wrong, Id say at a minimum to get a patch out, if u start on it the very second u get the exe its still 15 mins after servers are back up (since patch takes 3 hours we have plenty of time to check the offsets in the cold compile, but still I like to give it a few mins ingame as well just to make sure its all good and ready)
Anyway, Lets say spawnwinfo needs to be corrected, So the way I do that is:
9. I press g in ida and paste in the offset my structchecker has found for spawninfo.
10. I run a ida plugin that looks up the offsets in the struct from the previous patch and then it suggests where the new location is.
This is basically half automated, but still requires me to look at every single offset it suggests, cause I haven't figured out how
to automate this in a way where it can just read old struct, and output a new one automatically without any inspection at all from a human.
I suppose this is the point where you or someone smarter than me, can write a plugin that does that with 100% certainability(is that a real word?)
But for all intents and purposes, my plugin just jumps me to the locations in the struct it "thinks" are correct and I can then make a decision about
if it is good or not, and I click ok if I see its the right place, it then saves that location in the new struct, and moves on to the next structmember.
For SpawnInfo, I'd say that process, to do all the members, takes about 15-30 min or so, if they are all in the places we expect, and here is the conundrum,
IF they are not, I manually need to confirm the member and I look up functions that use the member in question, I pretty much know where to look due to experience over the years from fixing previous patches, and I suppose other devs have their own tricks/functions as well to look into when something is questionable.
When a manual inspection IS needed this process can easily take anywhere from a min to an hour, just for that eluding member...
This is especially true when new members have been added to the struct and my checker hasn't been configured to look them up yet...
11. I now have the offsets of all the spawninfo members in a raw format, basically it looks like this:
Rich (BB code):
vtable 0
pPrev 4
pNext 8
SpeedMultiplier 18
TimeStamp 30
Lastname 38
Y 64
X 68
SpeedY 70
SpeedX 74
SpeedZ 78
SpeedRun 7c
Heading 80
CameraAngle 90
FeetWet a1
Name a4
Type 125
UnknownHeight 13c
AvatarHeight2 140
Zone 438
Instance 43a
Z 6C
AvatarHeight 138
SpawnID 148
MasterID 1E4
HPMax 254
HPCurrent 1CC
GuildID 1F0
SpeedHeading 8C
Race eb8
Class 0EBC
Level 3A0
Sneak 52C
PvPFlag 21C
StandState 4D8
Light 271
RunSpeed 1C4
Linkdead 1AC
GM 215
LFG 3A8
BodyType 128
Anon 220
DisplayedName 0E4
AARank 2A0
GuildStatus 44C
Deity 444
Holding 418
Title 4D9
Suffix 450
LastTick 4CC
RespawnTimer 18C
UnderWater 94
GetMeleeRangeVar1 504
InnateETA 2D0
FishingETA 1FC
IsABoat 150
Mount 154
Rider 158
WhoFollowing 0E48
PetID 1DC
GroupAssistNPC 0E4C
RaidAssistNPC 0E50
GroupMarkNPC 0E5C
RaidMarkNPC 0E68
TargetOfTarget 0E74
CastingData 470
FishingEvent 264
Trader 1C8
Buyer 440
Mercenary 4D0
pcactorex 1020
InNonPCRaceIllusion 0EAC
Equipment 0F34
FaceStyle 0EB0
Gender 0EBD
FaceRelatedActorStruct 1028
HairStyle 0EB5
HairColor 0EB1
FacialHair 0EB6
FacialHairColor 0EB2
EyeColor1 eb3
EyeColor2 eb4
ArmorColor 0F00
pActorClient 0EA8
Heritage 0F28
Tattoo 0F2C
Details 0F30
ActorDef 0EBE
Animation 10C8
HideCorpse 11D4
WalkSpeed 11D0
InvitedToGroup 1218
GroupMemberTargeted 121C
Fellowship 13A0
CampfireY 1D84
CampfireX 1d88
CampfireZ 1d8c
CampfireZoneID 1d90
InstanceID 1d92
pSpawn 1EC8
Levitate 1ECC
ManaMax 318
ManaCurrent 448
EnduranceMax 42C
EnduranceCurrent 260
AFK 43C
HideMode 190
GetMeleeRangeVar2 1060
CampfireTimestamp 1D94
Campfire 1DA0
vtable2 1EC0
12. I then run that through an exe I wrote called structfixer.exe which takes a real struct, the old one for spawninfo, and inputs the new locations/realigns it, and pad the Unknowns, plus it keeps comments, and we end up with this: (1 sec)
Rich (BB code):
// actual size is 0x1F18 in Sep 13 2013 version of eqgame.exe (59A998)- eqmule
typedef struct _SPAWNINFO {
/*0x0000*/ void* vtable;
/*0x0004*/ struct _SPAWNINFO* pPrev;
/*0x0008*/ struct _SPAWNINFO* pNext;
/*0x000c*/ BYTE Unknown0x000c[0xc];
/*0x0018*/ FLOAT SpeedMultiplier;
/*0x001c*/ BYTE Unknown0x001c[0x14];
/*0x0030*/ DWORD TimeStamp;
/*0x0034*/ BYTE Unknown0x0034[0x4];
/*0x0038*/ CHAR Lastname[0x20];
/*0x0058*/ BYTE Unknown0x0058[0xc];
/*0x0064*/ FLOAT Y;
/*0x0068*/ FLOAT X;
/*0x006c*/ FLOAT Z;
/*0x0070*/ FLOAT SpeedY;
/*0x0074*/ FLOAT SpeedX;
/*0x0078*/ FLOAT SpeedZ;
/*0x007c*/ FLOAT SpeedRun;
/*0x0080*/ FLOAT Heading;
/*0x0084*/ BYTE Unknown0x0084[0x8];
/*0x008c*/ FLOAT SpeedHeading;
/*0x0090*/ FLOAT CameraAngle;
/*0x0094*/ BYTE UnderWater;
/*0x0095*/ BYTE Unknown0x0095[0xc];
/*0x00a1*/ BYTE FeetWet;
/*0x00a2*/ BYTE Unknown0x00a2[0x2];
/*0x00a4*/ CHAR Name[0x40]; // ie priest_of_discord00
/*0x00e4*/ CHAR DisplayedName[0x40]; // ie Priest of Discord
/*0x0124*/ BYTE Unknown0x0124;
/*0x0125*/ BYTE Type;
/*0x0126*/ BYTE Unknown0x0126[0x2];
/*0x0128*/ DWORD** BodyType;
/*0x012c*/ BYTE Unknown0x012c[0xc];
/*0x0138*/ FLOAT AvatarHeight; // height of avatar from groundwhen standing
/*0x013c*/ FLOAT UnknownHeight;
/*0x0140*/ FLOAT AvatarHeight2; // height of avatar from groundwhen crouched/sitting
/*0x0144*/ BYTE Unknown0x0144[0x4];
/*0x0148*/ DWORD SpawnID;
/*0x014c*/ BYTE Unknown0x014c[0x4];
/*0x0150*/ DWORD IsABoat; // 1 = a type of boat
/*0x0154*/ struct _SPAWNINFO* Mount; // NULL if no mount present
/*0x0158*/ struct _SPAWNINFO* Rider; // _SPAWNINFO of mount's rider
/*0x015c*/ BYTE Unknown0x015c[0x30];
/*0x018c*/ DWORD RespawnTimer; // TimeStamp of when RespawnWnd will close - 0 when you're alive
/*0x0190*/ BYTE HideMode;
/*0x0191*/ BYTE Unknown0x0191[0x1b];
/*0x01ac*/ BYTE Linkdead;
/*0x01ad*/ BYTE Unknown0x01ad[0x17];
/*0x01c4*/ FLOAT RunSpeed;//0.70 on runspeed 5...
/*0x01c8*/ DWORD Trader;//found in CEverQuest__RightClickedOnPlayer_x
/*0x01cc*/ LONG HPCurrent;
/*0x01d0*/ BYTE Unknown0x01d0[0xc];
/*0x01dc*/ DWORD PetID;
/*0x01e0*/ BYTE Unknown0x01e0[0x4];
/*0x01e4*/ DWORD MasterID;
/*0x01e8*/ BYTE Unknown0x01e8[0x8];
/*0x01f0*/ DWORD GuildID;
/*0x01f4*/ BYTE Unknown0x01f4[0x8];
/*0x01fc*/ DWORD FishingETA;
/*0x0200*/ BYTE Unknown0x0200[0x15];
/*0x0215*/ BYTE GM;
/*0x0216*/ BYTE Unknown0x0216[0x6];
/*0x021c*/ BYTE PvPFlag;
/*0x021d*/ BYTE Unknown0x021d[0x3];
/*0x0220*/ DWORD Anon;//found in EQPlayer__SetNameSpriteTint_x
/*0x0224*/ BYTE Unknown0x0224[0x30];
/*0x0254*/ DWORD HPMax;
/*0x0258*/ BYTE Unknown0x0258[0x8];
/*0x0260*/ DWORD EnduranceCurrent;
/*0x0264*/ BYTE FishingEvent;
/*0x0265*/ BYTE Unknown0x0265[0xc];
/*0x0271*/ BYTE Light;
/*0x0272*/ BYTE Unknown0x0272[0x2e];
/*0x02a0*/ DWORD AARank;
/*0x02a4*/ BYTE Unknown0x02a4[0x2c];
/*0x02d0*/ DWORD InnateETA; //Calculated TimeStamp when innate skill will be ready (LoH, HT, Bash)
/*0x02d4*/ BYTE Unknown0x02d4[0x44];
/*0x0318*/ DWORD ManaMax;
/*0x031c*/ BYTE Unknown0x031c[0x84];
/*0x03a0*/ BYTE Level;
/*0x03a1*/ BYTE Unknown0x03a1[0x7];
/*0x03a8*/ BYTE LFG;
/*0x03a9*/ BYTE Unknown0x03a9[0x6f];
/*0x0418*/ BYTE Holding;
/*0x0419*/ BYTE Unknown0x0419[0x13];
/*0x042c*/ DWORD EnduranceMax;
/*0x0430*/ BYTE Unknown0x0430[0x8];
/*0x0438*/ WORD Zone;
/*0x043a*/ WORD Instance;
/*0x043c*/ DWORD AFK;
/*0x0440*/ DWORD Buyer;
/*0x0444*/ DWORD Deity;
/*0x0448*/ DWORD ManaCurrent;
/*0x044c*/ DWORD GuildStatus;
/*0x0450*/ CHAR Suffix[0x20];
/*0x0470*/ struct _LAUNCHSPELLDATA CastingData; // size: 0x50
/*0x04c0*/ BYTE Unknown0x04c0[0xc];
/*0x04cc*/ DWORD LastTick;
/*0x04d0*/ BYTE Mercenary;
/*0x04d1*/ BYTE Unknown0x04d1[0x7];
/*0x04d8*/ BYTE StandState;
/*0x04d9*/ CHAR Title[0x20];
/*0x04f9*/ BYTE Unknown0x04f9[0xb];
/*0x0504*/ FLOAT GetMeleeRangeVar1; // used by GetMeleeRange
/*0x0508*/ BYTE Unknown0x0508[0x24];
/*0x052c*/ BYTE Sneak;
/*0x052d*/ BYTE Unknown0x052d[0x91b];
/*0x0e48*/ struct _SPAWNINFO* WhoFollowing; // NULL if autofollow off
/*0x0e4c*/ int GroupAssistNPC[0x1];
/*0x0e50*/ int RaidAssistNPC[0x3];
/*0x0e5c*/ int GroupMarkNPC[0x3];
/*0x0e68*/ int RaidMarkNPC[0x3];
/*0x0e74*/ int TargetOfTarget;
/*0x0e78*/ BYTE Unknown0x0e78[0x30];
/*0x0ea8*/ void* pActorClient; // start of ActorClient struct
/*0x0eac*/ BYTE InNonPCRaceIllusion;
/*0x0ead*/ BYTE Unknown0x0ead[0x3];
/*0x0eb0*/ BYTE FaceStyle;
/*0x0eb1*/ BYTE HairColor;
/*0x0eb2*/ BYTE FacialHairColor;
/*0x0eb3*/ BYTE EyeColor1;
/*0x0eb4*/ BYTE EyeColor2;
/*0x0eb5*/ BYTE HairStyle;
/*0x0eb6*/ BYTE FacialHair;
/*0x0eb7*/ BYTE Unknown0x0eb7;
/*0x0eb8*/ DWORD Race;
/*0x0ebc*/ BYTE Class;
/*0x0ebd*/ BYTE Gender;
/*0x0ebe*/ CHAR ActorDef[0x40];
/*0x0efe*/ BYTE Unknown0x0efe[0x2];
/*0x0f00*/ ARGBCOLOR ArmorColor[0x9];
/*0x0f24*/ BYTE Unknown0x0f24[0x4];
/*0x0f28*/ DWORD Heritage; //drakkin only face setting
/*0x0f2c*/ DWORD Tattoo; //drakkin only face setting
/*0x0f30*/ DWORD Details; //drakkin only face setting
/*0x0f34*/ struct _EQUIPMENT Equipment; // size 0xb4
/*0x0fe8*/ BYTE Unknown0x0fe8[0x38];
/*0x1020*/ VOID* pcactorex; // ActorInterface*
/*0x1024*/ BYTE Unknown0x1024[0x4];
/*0x1028*/ VOID* FaceRelatedActorStruct;
/*0x102c*/ BYTE Unknown0x102c[0x34];
/*0x1060*/ FLOAT GetMeleeRangeVar2; // used by GetMeleeRange
/*0x1064*/ BYTE Unknown0x1064[0x64];
/*0x10c8*/ DWORD Animation;
/*0x10cc*/ BYTE Unknown0x10cc[0x104];
/*0x11d0*/ FLOAT WalkSpeed;
/*0x11d4*/ DWORD HideCorpse;
/*0x11d8*/ BYTE Unknown0x11d8[0x40];
/*0x1218*/ BYTE InvitedToGroup;
/*0x1219*/ BYTE Unknown0x1219[0x3];
/*0x121c*/ DWORD GroupMemberTargeted; // 0xFFFFFFFF if no target, else 1 through 5
/*0x1220*/ BYTE Unknown0x1220[0x180];
/*0x13a0*/ struct _FELLOWSHIPINFO Fellowship; // size 0x9e4
/*0x1d84*/ FLOAT CampfireY;
/*0x1d88*/ FLOAT CampfireX;
/*0x1d8c*/ FLOAT CampfireZ;
/*0x1d90*/ WORD CampfireZoneID; // zone ID where campfire is
/*0x1d92*/ WORD InstanceID;
/*0x1d94*/ DWORD CampfireTimestamp; // CampfireTimestamp-FastTime()=time left on campfire
/*0x1d98*/ BYTE Unknown0x1d98[0x8];
/*0x1da0*/ DWORD Campfire; // do we have a campfire up?
/*0x1da4*/ BYTE Unknown0x1da4[0x11c];
/*0x1ec0*/ void* vtable2;
/*0x1ec4*/ BYTE Unknown0x1ec4[0x4];
/*0x1ec8*/ struct _SPAWNINFO* pSpawn;
/*0x1ecc*/ DWORD Levitate;
/*0x1ec8*/ BYTE Unknown0x1ec8[0x50];
/*0x1f18*/
} SPAWNINFO, *PSPAWNINFO;
13. Now I take that struct and paste it over the old one in eqdata.h (10 sec)
14. I now need to look up a couple of opcodes, namely EQ_BEGIN_ZONE and the END one, I suppose this could be automated, but it only takes me about a min to do it manually, and it would take me longer to automate a way to look them up, which is why I haven't gotten around to it.
Also, I like to do this part manually cause it helps me understand if there has been other changes as well since actually looking at the disassembly is very helpful in understanding whats new/removed/changed, (I can spend an hour or so just getting up to date with the new exes disassembly at this step, manually naming functions/familiarize myself with new structs/functions)
I feel that time is well spent, cause if u miss a couple patches, you will know what I mean, its hard to follow the flow, and will require you to spend so much more time in the future if you are not up to speed with whats done to different pieces of code in the exe.
15. There are also a few other things I need to look into like the chat history offset and so on, minor things but still takes a few mins (automate away this step if you like, but trust me, there are new things added quite often, so this is an ongoing task, you can create an autoupdater that works for one patch, and will be broken in the next...)
16. Ok, so at this point we can build and test, again, ingame is preferred even though Im always 99% certain it will work, I still like to go in game.
17. Update cvs, make a zip, make forum post, inform irc users, change irc topic (10 mins)
The above was only an example of what its like for ONE struct, but seriously, there are many others, for this September 13 patch, I had to correct multiple.
SpawnInfo, Contents, lootwindow, merchantwindow, mapwindow, tradewindow and a couple more I cant remember while typing this, but u can diff previous mq2 source with this sep 13 version and u will see which ones.
18. After all of the eqdata.h structs have been checked, The nightmare structs, the evil stepsisters of all structs, are still left to be done...
its the CXW and the CSW structs... I got this step mostly automated at this point, but I can tell you that when I did these the first time, it was an uphill battle.
I can easily spend an hour or two on the EQUIStructs.h alone, and Id be happy to see if anyone can do it faster, and in that case, please... teach me, no one would be happier than me to see all of that stuff being fully automated.
The manual work is pretty intense even if u get all structs corrected through automation (and I do have most of them, I add another one or so every other month, but it takes a few months since you only get one patch a month, to perfect them.) you still need to paste them into the source and build it, then do a live run...
If you really are going to reinvent the wheel on this one, be prepared to spend the next few months on it, finetuning your process every patchday until you have perfect automation, then watch your perfect automation become crap cause SOE made changes that you could not anticipate, and you will spend another few months on finetuning your process again
We are always looking for new devs that can carry some weight on patchday/support the ongoing efforts to get MQ2 out in a orderly and timely fashion, but don't be kidding yourself, its not an easy job and I don't believe there is a shortcut to success on this one, but im certainly open to any reasonable suggestions on how to improve the patchday process.