save the saved (normal and pp) locations
[gemrb.git] / gemrb / plugins / GAMImporter / GAMImporter.cpp
blobd02fbff0ca2a10e71b8a3cb8aa5d9b5f1c280511
1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "GAMImporter.h"
23 #include "globals.h"
24 #include "win32def.h"
26 #include "DataFileMgr.h"
27 #include "GameData.h"
28 #include "Interface.h"
29 #include "MapMgr.h"
30 #include "System/MemoryStream.h"
32 #include <cassert>
34 #define MAZE_DATA_SIZE 1720
35 #define FAMILIAR_FILL_SIZE 324
36 // if your compiler chokes on this, use -1 or 0xff whichever works for you
37 #define UNINITIALIZED_CHAR '\xff'
39 GAMImporter::GAMImporter(void)
41 str = NULL;
42 autoFree = false;
45 GAMImporter::~GAMImporter(void)
47 if (str && autoFree) {
48 delete( str );
52 bool GAMImporter::Open(DataStream* stream, bool autoFree)
54 if (stream == NULL) {
55 return false;
57 if (str) {
58 return false;
60 str = stream;
61 this->autoFree = autoFree;
62 char Signature[8];
63 str->Read( Signature, 8 );
64 if (strncmp( Signature, "GAMEV0.0", 8 ) == 0) {
65 version = GAM_VER_GEMRB;
66 PCSize = 0x160;
67 } else if (strncmp( Signature, "GAMEV2.0", 8 ) == 0) {
68 //soa (soa part of tob)
69 version = GAM_VER_BG2;
70 PCSize = 0x160;
71 } else if (strncmp( Signature, "GAMEV2.1", 8 ) == 0) {
72 //tob
73 version = GAM_VER_TOB;
74 PCSize = 0x160;
75 } else if (strncmp( Signature, "GAMEV1.0", 8 ) == 0) {
76 //bg1?
77 version = GAM_VER_BG;
78 PCSize = 0x160;
79 } else if (strncmp( Signature, "GAMEV2.2", 8 ) == 0) {
80 //iwd2
81 version = GAM_VER_IWD2;
82 PCSize = 0x340;
83 } else if (strncmp( Signature, "GAMEV1.1", 8 ) == 0) {
84 //iwd, torment, totsc
85 if (core->HasFeature(GF_HAS_KAPUTZ) ) { //pst
86 PCSize = 0x168;
87 version = GAM_VER_PST;
88 //sound folder name takes up this space,
89 //so it is handy to make this check
90 } else if ( core->HasFeature(GF_SOUNDFOLDERS) ) {
91 PCSize = 0x180;
92 version = GAM_VER_IWD;
93 } else {
94 PCSize = 0x160;
95 version=GAM_VER_BG;
97 } else {
98 printMessage( "GAMImporter","This file is not a valid GAM File\n", LIGHT_RED );
99 return false;
102 return true;
105 Game* GAMImporter::LoadGame(Game *newGame, int ver_override)
107 unsigned int i;
109 // saving in original version requires the original version
110 // otherwise it is set to 0 at construction time
111 if (core->SaveAsOriginal) {
112 // HACK: default icewind2.gam is 2.0! handled by script
113 if(ver_override) {
114 newGame->version = ver_override;
116 else {
117 newGame->version = version;
121 ieDword GameTime;
122 str->ReadDword( &GameTime );
123 newGame->GameTime = GameTime*AI_UPDATE_TIME;
125 str->ReadWord( &newGame->WhichFormation );
126 for (i = 0; i < 5; i++) {
127 str->ReadWord( &newGame->Formations[i] );
129 //hack for PST
130 if (version==GAM_VER_PST) {
131 newGame->Formations[0] = newGame->WhichFormation;
132 newGame->WhichFormation = 0;
134 str->ReadDword( &newGame->PartyGold );
135 //npc count in party???
136 str->ReadWord( &newGame->NpcInParty );
137 str->ReadWord( &newGame->WeatherBits );
138 str->ReadDword( &PCOffset );
139 str->ReadDword( &PCCount );
140 //these fields are not really used by any engine, and never saved
141 //str->ReadDword( &UnknownOffset );
142 //str->ReadDword( &UnknownCount );
143 str->Seek( 8, GEM_CURRENT_POS);
144 str->ReadDword( &NPCOffset );
145 str->ReadDword( &NPCCount );
146 str->ReadDword( &GlobalOffset );
147 str->ReadDword( &GlobalCount );
148 str->ReadResRef( newGame->CurrentArea );
149 str->ReadDword( &newGame->Unknown48 );
150 str->ReadDword( &JournalCount );
151 str->ReadDword( &JournalOffset );
152 switch (version) {
153 default:
154 MazeOffset = 0;
155 str->ReadDword( &newGame->Reputation );
156 str->ReadResRef( newGame->CurrentArea ); // FIXME: see above
157 memcpy(newGame->AnotherArea, newGame->CurrentArea, sizeof(ieResRef) );
158 str->ReadDword( &newGame->ControlStatus );
159 str->ReadDword( &KillVarsCount ); //this is still unknown
160 str->ReadDword( &FamiliarsOffset );
161 str->ReadDword( &SavedLocOffset );
162 str->ReadDword( &SavedLocCount );
163 str->ReadDword( &newGame->RealTime);
164 str->ReadDword( &PPLocOffset );
165 str->ReadDword( &PPLocCount );
166 str->Seek( 52, GEM_CURRENT_POS);
167 break;
169 case GAM_VER_PST:
170 str->ReadDword( &MazeOffset );
171 str->ReadDword( &newGame->Reputation );
172 str->ReadResRef( newGame->AnotherArea );
173 str->ReadDword( &KillVarsOffset );
174 str->ReadDword( &KillVarsCount );
175 str->ReadDword( &FamiliarsOffset ); //bestiary
176 str->ReadResRef( newGame->AnotherArea ); //yet another area
177 SavedLocOffset = 0;
178 SavedLocCount = 0;
179 PPLocOffset = 0;
180 PPLocCount = 0;
181 str->Seek( 64, GEM_CURRENT_POS);
182 break;
185 if (!newGame->CurrentArea[0]) {
186 // 0 - normal, 1 - tutorial, 2 - extension
187 AutoTable tm("STARTARE");
188 ieDword playmode = 0;
189 //only bg2 has 9 rows (iwd's have 6 rows - normal+extension)
190 if (tm && tm->GetRowCount()==9) {
191 core->GetDictionary()->Lookup( "PlayMode", playmode );
192 playmode *= 3;
195 const char* resref = tm->QueryField( playmode );
196 strnlwrcpy( newGame->CurrentArea, resref, 8 );
199 //Loading PCs
200 PluginHolder<ActorMgr> aM(IE_CRE_CLASS_ID);
201 for (i = 0; i < PCCount; i++) {
202 str->Seek( PCOffset + ( i * PCSize ), GEM_STREAM_START );
203 Actor *actor = GetActor( aM, true );
204 newGame->JoinParty( actor, actor->Selected?JP_SELECT:0 );
207 //Loading NPCs
208 for (i = 0; i < NPCCount; i++) {
209 str->Seek( NPCOffset + ( i * PCSize ), GEM_STREAM_START );
210 Actor *actor = GetActor( aM, false );
211 newGame->AddNPC( actor );
214 //apparently BG1/IWD2 relies on this, if chapter is unset, it is
215 //set to -1, hopefully it won't break anything
216 newGame->locals->SetAt("CHAPTER", (ieDword) -1);
218 // load initial values from var.var
219 newGame->locals->LoadInitialValues("GLOBAL");
221 //Loading Global Variables
222 ieVariable Name;
223 Name[32] = 0;
224 str->Seek( GlobalOffset, GEM_STREAM_START );
225 for (i = 0; i < GlobalCount; i++) {
226 ieDword Value;
227 str->Read( Name, 32 );
228 str->Seek( 8, GEM_CURRENT_POS );
229 str->ReadDword( &Value );
230 str->Seek( 40, GEM_CURRENT_POS );
231 newGame->locals->SetAt( Name, Value );
233 if(core->HasFeature(GF_HAS_KAPUTZ) ) {
234 newGame->kaputz = new Variables();
235 newGame->kaputz->SetType( GEM_VARIABLES_INT );
236 newGame->kaputz->ParseKey( 1 );
237 // load initial values from var.var
238 newGame->kaputz->LoadInitialValues("KAPUTZ");
239 str->Seek( KillVarsOffset, GEM_STREAM_START );
240 for (i = 0; i < KillVarsCount; i++) {
241 ieDword Value;
242 str->Read( Name, 32 );
243 str->Seek( 8, GEM_CURRENT_POS );
244 str->ReadDword( &Value );
245 str->Seek( 40, GEM_CURRENT_POS );
246 newGame->kaputz->SetAt( Name, Value );
250 //Loading Journal entries
251 str->Seek( JournalOffset, GEM_STREAM_START );
252 for (i = 0; i < JournalCount; i++) {
253 GAMJournalEntry* je = GetJournalEntry();
254 newGame->AddJournalEntry( je );
257 if (version == GAM_VER_PST) {
258 //loading maze
259 if (MazeOffset) {
260 newGame->mazedata = (ieByte*)malloc(MAZE_DATA_SIZE);
261 str->Seek(MazeOffset, GEM_STREAM_START );
262 str->Read(newGame->mazedata, MAZE_DATA_SIZE);
264 str->Seek( FamiliarsOffset, GEM_STREAM_START );
265 } else {
266 if (FamiliarsOffset) {
267 str->Seek( FamiliarsOffset, GEM_STREAM_START );
268 for (i=0; i<9;i++) {
269 str->ReadResRef( newGame->GetFamiliar(i) );
271 } else {
272 //clear these fields up
273 for (i=0;i<9;i++) {
274 memset(newGame->GetFamiliar(i), 0, sizeof(ieResRef));
278 // Loading known creatures array (beasts)
279 if(core->GetBeastsINI() != NULL) {
280 int beasts_count = BESTIARY_SIZE;
281 newGame->beasts = (ieByte*)calloc(sizeof(ieByte),beasts_count);
282 if(FamiliarsOffset) {
283 str->Read( newGame->beasts, beasts_count );
287 //TODO: these need to be corrected!
288 if (SavedLocCount && SavedLocOffset) {
289 ieWord PosX, PosY;
291 str->Seek( SavedLocOffset, GEM_STREAM_START );
292 for (i=0; i<SavedLocCount; i++) {
293 GAMLocationEntry *gle = newGame->GetSavedLocationEntry(i);
294 str->ReadResRef( gle->AreaResRef );
295 str->ReadWord( &PosX );
296 str->ReadWord( &PosY );
297 gle->Pos.x=PosX;
298 gle->Pos.y=PosY;
302 if (PPLocCount && PPLocOffset) {
303 ieWord PosX, PosY;
305 str->Seek( PPLocOffset, GEM_STREAM_START );
306 for (i=0; i<PPLocCount; i++) {
307 GAMLocationEntry *gle = newGame->GetPlaneLocationEntry(i);
308 str->ReadResRef( gle->AreaResRef );
309 str->ReadWord( &PosX );
310 str->ReadWord( &PosY );
311 gle->Pos.x=PosX;
312 gle->Pos.y=PosY;
315 return newGame;
318 void SanityCheck(ieWord a,ieWord &b,const char *message)
320 if (a==0xffff) {
321 b=0xffff;
322 return;
324 if (b==0xffff) {
325 printMessage("GAMImporter"," ",LIGHT_RED);
326 printf("Invalid Slot Enabler caught: %s!\n", message);
327 b=0;
331 Actor* GAMImporter::GetActor(Holder<ActorMgr> aM, bool is_in_party )
333 unsigned int i;
334 PCStruct pcInfo;
335 ieDword tmpDword;
336 ieWord tmpWord;
338 memset( &pcInfo,0,sizeof(pcInfo) );
339 str->ReadWord( &pcInfo.Selected );
340 str->ReadWord( &pcInfo.PartyOrder );
341 str->ReadDword( &pcInfo.OffsetToCRE );
342 str->ReadDword( &pcInfo.CRESize );
343 str->ReadResRef( pcInfo.CREResRef );
344 str->ReadDword( &pcInfo.Orientation );
345 str->ReadResRef( pcInfo.Area );
346 str->ReadWord( &pcInfo.XPos );
347 str->ReadWord( &pcInfo.YPos );
348 str->ReadWord( &pcInfo.ViewXPos );
349 str->ReadWord( &pcInfo.ViewYPos );
350 str->ReadWord( &pcInfo.ModalState ); //see Modal.ids
351 str->ReadWord( &pcInfo.Happiness );
352 str->Read( &pcInfo.Unknown2c, 96 );
354 if (version==GAM_VER_GEMRB || version==GAM_VER_IWD2) {
355 ieResRef tmp;
357 for (i = 0; i < 4; i++) {
358 str->ReadWord( &pcInfo.QuickWeaponSlot[i] );
359 str->ReadWord( &pcInfo.QuickWeaponSlot[i+4] );
361 for (i = 0; i < 4; i++) {
362 str->ReadWord( &tmpWord );
363 SanityCheck( pcInfo.QuickWeaponSlot[i], tmpWord, "weapon");
364 pcInfo.QuickWeaponHeader[i]=tmpWord;
365 str->ReadWord( &tmpWord );
366 SanityCheck( pcInfo.QuickWeaponSlot[i+4], tmpWord, "weapon");
367 pcInfo.QuickWeaponHeader[i+4]=tmpWord;
369 for (i = 0; i < MAX_QSLOTS; i++) {
370 str->Read( &pcInfo.QuickSpellResRef[i], 8 );
372 str->Read( &pcInfo.QuickSpellClass, MAX_QSLOTS ); //9 bytes
374 str->Seek( 1, GEM_CURRENT_POS); //skipping a padding byte
375 for (i = 0; i < 3; i++) {
376 str->ReadWord( &pcInfo.QuickItemSlot[i] );
378 for (i = 0; i < 3; i++) {
379 str->ReadWord( &tmpWord );
380 SanityCheck( pcInfo.QuickItemSlot[i], tmpWord, "item");
381 pcInfo.QuickItemHeader[i]=tmpWord;
383 pcInfo.QuickItemHeader[3]=0xffff;
384 pcInfo.QuickItemHeader[4]=0xffff;
385 if (version == GAM_VER_IWD2) {
386 //quick innates
387 //we spare some memory and time by storing them in the same place
388 //this may be slightly buggy because IWD2 doesn't clear the
389 //fields, but QuickSpellClass is set correctly, problem is
390 //that GemRB doesn't clear QuickSpellClass
391 for (i = 0; i < MAX_QSLOTS; i++) {
392 str->Read( tmp, 8 );
393 if ((tmp[0]!=0) && (pcInfo.QuickSpellResRef[0]==0)) {
394 memcpy( pcInfo.QuickSpellResRef[i], tmp, 8);
395 //innates
396 pcInfo.QuickSpellClass[i]=0xff;
399 //recently discovered fields (bard songs)
400 //str->Seek( 72, GEM_CURRENT_POS);
401 for(i = 0; i<MAX_QSLOTS;i++) {
402 str->Read( tmp, 8 );
403 if ((tmp[0]!=0) && (pcInfo.QuickSpellResRef[0]==0)) {
404 memcpy( pcInfo.QuickSpellResRef[i], tmp, 8);
405 //bardsongs
406 pcInfo.QuickSpellClass[i]=0xfe;
410 //QuickSlots are customisable in iwd2 and GemRB
411 //thus we adopt the iwd2 style actor info
412 for (i=0;i<MAX_QSLOTS;i++) {
413 str->ReadDword( &tmpDword );
414 pcInfo.QSlots[i] = (ieByte) tmpDword;
416 } else {
417 for (i = 0; i < 4; i++) {
418 str->ReadWord( &pcInfo.QuickWeaponSlot[i] );
420 for (i = 0; i < 4; i++) {
421 str->ReadWord( &tmpWord );
422 SanityCheck( pcInfo.QuickWeaponSlot[i], tmpWord, "weapon");
423 pcInfo.QuickWeaponHeader[i]=tmpWord;
425 for (i = 0; i < 3; i++) {
426 str->Read( &pcInfo.QuickSpellResRef[i], 8 );
428 if (version==GAM_VER_PST) { //Torment
429 for (i = 0; i < 5; i++) {
430 str->ReadWord( &pcInfo.QuickItemSlot[i] );
432 for (i = 0; i < 5; i++) {
433 str->ReadWord( &tmpWord );
434 SanityCheck( pcInfo.QuickItemSlot[i], tmpWord, "item");
435 pcInfo.QuickItemHeader[i]=tmpWord;
437 //str->Seek( 10, GEM_CURRENT_POS ); //enabler fields
438 } else {
439 for (i = 0; i < 3; i++) {
440 str->ReadWord( &pcInfo.QuickItemSlot[i] );
442 for (i = 0; i < 3; i++) {
443 str->ReadWord( &tmpWord );
444 SanityCheck( pcInfo.QuickItemSlot[i], tmpWord, "item");
445 pcInfo.QuickItemHeader[i]=tmpWord;
447 //str->Seek( 6, GEM_CURRENT_POS ); //enabler fields
449 pcInfo.QSlots[0] = 0xff; //(invalid, will be regenerated)
451 str->Read( &pcInfo.Name, 32 );
452 str->ReadDword( &pcInfo.TalkCount );
454 ieDword pos = str->GetPos();
456 Actor* actor = NULL;
457 tmpWord = is_in_party ? (pcInfo.PartyOrder + 1) : 0;
459 if (pcInfo.OffsetToCRE) {
460 str->Seek( pcInfo.OffsetToCRE, GEM_STREAM_START );
461 void* Buffer = malloc( pcInfo.CRESize );
462 str->Read( Buffer, pcInfo.CRESize );
463 //somehow autofree MemoryStream doesn't work on msvc 7.0
464 //separate heap for dll's?
465 MemoryStream* ms = new MemoryStream( Buffer, pcInfo.CRESize, false );
466 if (ms) {
467 aM->Open( ms, true );
468 actor = aM->GetActor(tmpWord);
470 free (Buffer);
472 //torment has them as 0 or -1
473 if (pcInfo.Name[0]!=0 && pcInfo.Name[0]!=UNINITIALIZED_CHAR) {
474 actor->SetName(pcInfo.Name,0); //setting both names
476 actor->TalkCount = pcInfo.TalkCount;
477 } else {
478 DataStream* ds = gamedata->GetResource(
479 pcInfo.CREResRef, IE_CRE_CLASS_ID );
480 //another plugin cannot free memory stream from this plugin
481 //so auto free is a no-no
482 if (ds) {
483 aM->Open( ds, true );
484 actor = aM->GetActor(pcInfo.PartyOrder);
487 if (!actor) {
488 return actor;
492 str->Seek(pos, GEM_STREAM_START);
494 actor->CreateStats();
495 PCStatsStruct *ps = actor->PCStats;
496 GetPCStats(ps);
497 memcpy(ps->QSlots, pcInfo.QSlots, sizeof(pcInfo.QSlots) );
498 memcpy(ps->QuickSpells, pcInfo.QuickSpellResRef, MAX_QSLOTS*sizeof(ieResRef) );
499 memcpy(ps->QuickSpellClass, pcInfo.QuickSpellClass, MAX_QSLOTS );
500 memcpy(ps->QuickWeaponSlots, pcInfo.QuickWeaponSlot, MAX_QUICKWEAPONSLOT*sizeof(ieWord) );
501 memcpy(ps->QuickWeaponHeaders, pcInfo.QuickWeaponHeader, MAX_QUICKWEAPONSLOT*sizeof(ieWord) );
502 memcpy(ps->QuickItemSlots, pcInfo.QuickItemSlot, MAX_QUICKITEMSLOT*sizeof(ieWord) );
503 memcpy(ps->QuickItemHeaders, pcInfo.QuickItemHeader, MAX_QUICKITEMSLOT*sizeof(ieWord) );
504 actor->Destination.x = actor->Pos.x = pcInfo.XPos;
505 actor->Destination.y = actor->Pos.y = pcInfo.YPos;
506 strcpy( actor->Area, pcInfo.Area );
507 actor->SetOrientation( pcInfo.Orientation,0 );
508 actor->TalkCount = pcInfo.TalkCount;
509 actor->ModalState = pcInfo.ModalState;
510 actor->SetModalSpell(pcInfo.ModalState, 0);
512 actor->SetPersistent( tmpWord );
514 actor->Selected = pcInfo.Selected;
515 return actor;
518 void GAMImporter::GetPCStats (PCStatsStruct *ps)
520 int i;
522 str->ReadDword( &ps->BestKilledName );
523 str->ReadDword( &ps->BestKilledXP );
524 str->ReadDword( &ps->AwayTime );
525 str->ReadDword( &ps->JoinDate );
526 str->ReadDword( &ps->unknown10 );
527 str->ReadDword( &ps->KillsChapterXP );
528 str->ReadDword( &ps->KillsChapterCount );
529 str->ReadDword( &ps->KillsTotalXP );
530 str->ReadDword( &ps->KillsTotalCount );
531 for (i = 0; i <= 3; i++) {
532 str->ReadResRef( ps->FavouriteSpells[i] );
534 for (i = 0; i <= 3; i++)
535 str->ReadWord( &ps->FavouriteSpellsCount[i] );
537 for (i = 0; i <= 3; i++) {
538 str->ReadResRef( ps->FavouriteWeapons[i] );
540 for (i = 0; i <= 3; i++)
541 str->ReadWord( &ps->FavouriteWeaponsCount[i] );
543 str->ReadResRef( ps->SoundSet );
545 if (core->HasFeature(GF_SOUNDFOLDERS) ) {
546 str->Read( ps->SoundFolder, 32);
550 GAMJournalEntry* GAMImporter::GetJournalEntry()
552 GAMJournalEntry* j = new GAMJournalEntry();
554 str->ReadDword( &j->Text );
555 str->ReadDword( &j->GameTime );
556 //this could be wrong, most likely these are 2 words, or a dword
557 str->Read( &j->Chapter, 1 );
558 str->Read( &j->unknown09, 1 );
559 str->Read( &j->Section, 1 );
560 str->Read( &j->Group, 1 ); // this is a GemRB extension
562 return j;
565 int GAMImporter::GetStoredFileSize(Game *game)
567 int headersize;
568 unsigned int i;
570 //moved this here, so one can disable killvars in a pst style game
571 //or enable them in gemrb
572 if(core->HasFeature(GF_HAS_KAPUTZ) ) {
573 KillVarsCount = game->kaputz->GetCount();
574 } else {
575 KillVarsCount = 0;
577 switch(game->version)
579 case GAM_VER_GEMRB:
580 headersize = 0xb4;
581 PCSize = 0x160;
582 break;
583 case GAM_VER_IWD:
584 headersize = 0xb4;
585 PCSize = 0x180;
586 break;
587 case GAM_VER_BG:
588 case GAM_VER_BG2:
589 case GAM_VER_TOB:
590 headersize = 0xb4;
591 PCSize = 0x160;
592 break;
593 case GAM_VER_IWD2:
594 headersize = 0xb4;
595 PCSize = 0x340;
596 break;
597 case GAM_VER_PST:
598 headersize = 0xb8;
599 PCSize = 0x168;
600 break;
601 default:
602 return -1;
604 PCOffset = headersize;
606 PluginHolder<ActorMgr> am(IE_CRE_CLASS_ID);
607 PCCount = game->GetPartySize(false);
608 headersize += PCCount * PCSize;
609 for (i = 0;i<PCCount; i++) {
610 Actor *ac = game->GetPC(i, false);
611 headersize += am->GetStoredFileSize(ac);
613 NPCOffset = headersize;
615 NPCCount = game->GetNPCCount();
616 headersize += NPCCount * PCSize;
617 for (i = 0;i<NPCCount; i++) {
618 Actor *ac = game->GetNPC(i);
619 headersize += am->GetStoredFileSize(ac);
622 if (game->mazedata) {
623 MazeOffset = headersize;
624 headersize += MAZE_DATA_SIZE;
625 } else {
626 MazeOffset = 0;
629 GlobalOffset = headersize;
631 GlobalCount = game->locals->GetCount();
632 headersize += GlobalCount * 84;
633 JournalOffset = headersize;
635 JournalCount = game->GetJournalCount();
636 headersize += JournalCount * 12;
638 KillVarsOffset = headersize;
639 if (KillVarsCount) {
640 headersize += KillVarsCount * 84;
643 if (game->version==GAM_VER_BG) {
644 FamiliarsOffset = 0;
645 } else {
646 FamiliarsOffset = headersize;
647 if (core->GetBeastsINI()) {
648 headersize += BESTIARY_SIZE;
650 if (game->version!=GAM_VER_PST) {
651 headersize += 9 * 8 + 82 * 4;
655 SavedLocOffset = headersize;
656 SavedLocCount = game->GetSavedLocationCount();
657 headersize +=SavedLocCount*20;
659 PPLocOffset = headersize;
660 PPLocCount = game->GetPlaneLocationCount();
662 return headersize + PPLocCount * 20;
665 int GAMImporter::PutJournals(DataStream *stream, Game *game)
667 for (unsigned int i=0;i<JournalCount;i++) {
668 GAMJournalEntry *j = game->GetJournalEntry(i);
670 stream->WriteDword( &j->Text );
671 stream->WriteDword( &j->GameTime );
672 //this could be wrong, most likely these are 2 words, or a dword
673 stream->Write( &j->Chapter, 1 );
674 stream->Write( &j->unknown09, 1 );
675 stream->Write( &j->Section, 1 );
676 stream->Write( &j->Group, 1 ); // this is a GemRB extension
679 return 0;
682 //only in ToB
683 int GAMImporter::PutSavedLocations(DataStream *stream, Game *game)
685 ieWord tmpWord;
687 for (unsigned int i=0;i<SavedLocCount;i++) {
688 GAMLocationEntry *j = game->GetSavedLocationEntry(i);
690 stream->WriteResRef(j->AreaResRef);
691 tmpWord = j->Pos.x;
692 stream->WriteWord(&tmpWord);
693 tmpWord = j->Pos.y;
694 stream->WriteWord(&tmpWord);
696 return 0;
699 int GAMImporter::PutPlaneLocations(DataStream *stream, Game *game)
701 ieWord tmpWord;
703 for (unsigned int i=0;i<PPLocCount;i++) {
704 GAMLocationEntry *j = game->GetPlaneLocationEntry(i);
706 stream->WriteResRef(j->AreaResRef);
707 tmpWord = j->Pos.x;
708 stream->WriteWord(&tmpWord);
709 tmpWord = j->Pos.y;
710 stream->WriteWord(&tmpWord);
712 return 0;
715 //only in PST
716 int GAMImporter::PutKillVars(DataStream *stream, Game *game)
718 char filling[40];
719 ieVariable tmpname;
720 Variables::iterator pos=NULL;
721 const char *name;
722 ieDword value;
724 memset(filling,0,sizeof(filling) );
725 for (unsigned int i=0;i<KillVarsCount;i++) {
726 //global variables are locals for game, that's why the local/global confusion
727 pos=game->kaputz->GetNextAssoc( pos, name, value);
728 strnspccpy(tmpname,name,32);
729 stream->Write( tmpname, 32);
730 stream->Write( filling, 8);
731 stream->WriteDword( &value);
732 //40 bytes of empty crap
733 stream->Write( filling, 40);
735 return 0;
738 int GAMImporter::PutVariables(DataStream *stream, Game *game)
740 char filling[40];
741 ieVariable tmpname;
742 Variables::iterator pos=NULL;
743 const char *name;
744 ieDword value;
746 memset(filling,0,sizeof(filling) );
747 for (unsigned int i=0;i<GlobalCount;i++) {
748 //global variables are locals for game, that's why the local/global confusion
749 pos=game->locals->GetNextAssoc( pos, name, value);
750 strnspccpy(tmpname, name, 32);
751 stream->Write( tmpname, 32);
752 stream->Write( filling, 8);
753 stream->WriteDword( &value);
754 //40 bytes of empty crap
755 stream->Write( filling, 40);
757 return 0;
760 int GAMImporter::PutHeader(DataStream *stream, Game *game)
762 int i;
763 char Signature[10];
764 ieDword tmpDword;
766 memcpy( Signature, "GAMEV0.0", 8);
767 Signature[5]+=game->version/10;
768 if (game->version==GAM_VER_PST || game->version==GAM_VER_BG) { //pst/bg1 saved version
769 Signature[7]+=1;
771 else {
772 Signature[7]+=game->version%10;
774 stream->Write( Signature, 8);
775 //using Signature for padding
776 memset(Signature, 0, sizeof(Signature));
777 tmpDword = game->GameTime/AI_UPDATE_TIME;
778 stream->WriteDword( &tmpDword );
779 //pst has a single preset of formations
780 if (game->version==GAM_VER_PST) {
781 stream->WriteWord( &game->Formations[0]);
782 stream->Write( Signature, 10);
783 } else {
784 stream->WriteWord( &game->WhichFormation );
785 for(i=0;i<5;i++) {
786 stream->WriteWord( &game->Formations[i]);
789 stream->WriteDword( &game->PartyGold );
790 //hack because we don't need this
791 game->NpcInParty=PCCount-1;
792 stream->WriteWord( &game->NpcInParty );
793 stream->WriteWord( &game->WeatherBits );
794 stream->WriteDword( &PCOffset );
795 stream->WriteDword( &PCCount );
796 //these fields are zeroed in any original savegame
797 tmpDword = 0;
798 stream->WriteDword( &tmpDword );
799 stream->WriteDword( &tmpDword );
800 stream->WriteDword( &NPCOffset );
801 stream->WriteDword( &NPCCount );
802 stream->WriteDword( &GlobalOffset );
803 stream->WriteDword( &GlobalCount );
804 stream->WriteResRef( game->CurrentArea );
805 //this is always 0xffffffff
806 stream->WriteDword( &game->Unknown48 );
807 stream->WriteDword( &JournalCount );
808 stream->WriteDword( &JournalOffset );
810 switch(game->version) {
811 case GAM_VER_GEMRB:
812 case GAM_VER_BG:
813 case GAM_VER_IWD:
814 case GAM_VER_BG2:
815 case GAM_VER_TOB:
816 case GAM_VER_IWD2:
817 stream->WriteDword( &game->Reputation );
818 stream->WriteResRef( game->CurrentArea );
819 stream->WriteDword( &game->ControlStatus );
820 stream->WriteDword( &tmpDword);
821 stream->WriteDword( &FamiliarsOffset);
822 stream->WriteDword( &SavedLocOffset);
823 stream->WriteDword( &SavedLocCount);
824 break;
825 case GAM_VER_PST:
826 stream->WriteDword( &MazeOffset );
827 stream->WriteDword( &game->Reputation );
828 stream->WriteResRef( game->CurrentArea );
829 stream->WriteDword( &KillVarsOffset );
830 stream->WriteDword( &KillVarsCount );
831 stream->WriteDword( &FamiliarsOffset );
832 stream->WriteResRef( game->CurrentArea ); //again
833 break;
835 stream->WriteDword( &game->RealTime ); //this isn't correct, this field is the realtime
836 stream->WriteDword( &PPLocOffset);
837 stream->WriteDword( &PPLocCount);
838 char filling[52];
839 memset( filling, 0, sizeof(filling) );
840 stream->Write( &filling, 52); //unknown
841 return 0;
844 int GAMImporter::PutActor(DataStream *stream, Actor *ac, ieDword CRESize, ieDword CREOffset, ieDword version)
846 int i;
847 ieDword tmpDword;
848 ieWord tmpWord;
849 char filling[194];
851 memset(filling,0,sizeof(filling) );
852 if (ac->Selected) {
853 tmpWord=1;
854 } else {
855 tmpWord=0;
858 stream->WriteWord( &tmpWord);
859 tmpWord = ac->InParty-1;
860 stream->WriteWord( &tmpWord);
862 stream->WriteDword( &CREOffset);
863 stream->WriteDword( &CRESize);
864 //creature resref is always unused in saved games
865 //BG1 doesn't even like the * in there, zero fill
866 //seems to be accepted by all
867 stream->Write( filling, 8);
868 tmpDword = ac->GetOrientation();
869 stream->WriteDword( &tmpDword);
870 stream->WriteResRef(ac->Area);
871 tmpWord = ac->Pos.x;
872 stream->WriteWord( &tmpWord);
873 tmpWord = ac->Pos.y;
874 stream->WriteWord( &tmpWord);
875 //no viewport, we cheat
876 tmpWord = ac->Pos.x-core->Width/2;
877 stream->WriteWord( &tmpWord);
878 tmpWord = ac->Pos.y-core->Height/2;
879 stream->WriteWord( &tmpWord);
880 tmpWord = (ieWord) ac->ModalState;
881 stream->WriteWord( &tmpWord);
882 tmpWord = 0; //happiness
883 stream->WriteWord( &tmpWord);
884 //a field of zeroes
885 stream->Write(filling, 96);
887 //quickweapons
888 if (version==GAM_VER_IWD2 || version==GAM_VER_GEMRB) {
889 for (i=0;i<4;i++) {
890 stream->WriteWord( ac->PCStats->QuickWeaponSlots+i);
891 stream->WriteWord( ac->PCStats->QuickWeaponSlots+4+i);
893 for (i=0;i<4;i++) {
894 stream->WriteWord( ac->PCStats->QuickWeaponHeaders+i);
895 stream->WriteWord( ac->PCStats->QuickWeaponHeaders+4+i);
897 } else {
898 for (i=0;i<4;i++) {
899 stream->WriteWord( ac->PCStats->QuickWeaponSlots+i);
901 for (i=0;i<4;i++) {
902 stream->WriteWord( ac->PCStats->QuickWeaponHeaders+i);
906 //quickspells
907 if (version==GAM_VER_IWD2 || version==GAM_VER_GEMRB) {
908 for (i=0;i<MAX_QSLOTS;i++) {
909 if ( (ieByte) ac->PCStats->QuickSpellClass[i]>=0xfe) {
910 stream->Write(filling,8);
911 } else {
912 stream->Write(ac->PCStats->QuickSpells[i],8);
915 //quick spell classes, clear the field for iwd2 if it is
916 //a bard song/innate slot (0xfe or 0xff)
917 memcpy(filling, ac->PCStats->QuickSpellClass, MAX_QSLOTS);
918 if (version==GAM_VER_IWD2) {
919 for(i=0;i<MAX_QSLOTS;i++) {
920 if((ieByte) filling[i]>=0xfe) {
921 filling[i]=0;
925 stream->Write(filling,10);
926 memset(filling,0,sizeof(filling) );
927 } else {
928 for (i=0;i<3;i++) {
929 stream->Write(ac->PCStats->QuickSpells[i],8);
933 //quick items
934 switch (version) {
935 case GAM_VER_PST: case GAM_VER_GEMRB:
936 for (i=0;i<MAX_QUICKITEMSLOT;i++) {
937 stream->WriteWord(ac->PCStats->QuickItemSlots+i);
939 for (i=0;i<MAX_QUICKITEMSLOT;i++) {
940 stream->WriteWord(ac->PCStats->QuickItemHeaders+i);
942 break;
943 default:
944 for (i=0;i<3;i++) {
945 stream->WriteWord(ac->PCStats->QuickItemSlots+i);
947 for (i=0;i<3;i++) {
948 stream->WriteWord(ac->PCStats->QuickItemHeaders+i);
950 break;
953 //innates, bard songs and quick slots are saved only in iwd2
954 if (version==GAM_VER_IWD2 || version==GAM_VER_GEMRB) {
955 for (i=0;i<MAX_QSLOTS;i++) {
956 if ( (ieByte) ac->PCStats->QuickSpellClass[i]==0xff) {
957 stream->Write(ac->PCStats->QuickSpells[i],8);
958 } else {
959 stream->Write(filling,8);
962 for (i=0;i<MAX_QSLOTS;i++) {
963 if ((ieByte) ac->PCStats->QuickSpellClass[i]==0xfe) {
964 stream->Write(ac->PCStats->QuickSpells[i],8);
965 } else {
966 stream->Write(filling,8);
969 for (i=0;i<MAX_QSLOTS;i++) {
970 tmpDword = ac->PCStats->QSlots[i];
971 stream->WriteDword( &tmpDword);
975 if (ac->LongStrRef==0xffffffff) {
976 strncpy(filling, ac->LongName, 32);
977 } else {
978 char *tmpstr = core->GetString(ac->LongStrRef, IE_STR_STRREFOFF);
979 strncpy(filling, tmpstr, 32);
980 core->FreeString( tmpstr );
982 stream->Write( filling, 32);
983 memset(filling,0,32);
984 stream->WriteDword( &ac->TalkCount);
985 stream->WriteDword( &ac->PCStats->BestKilledName);
986 stream->WriteDword( &ac->PCStats->BestKilledXP);
987 stream->WriteDword( &ac->PCStats->AwayTime);
988 stream->WriteDword( &ac->PCStats->JoinDate);
989 stream->WriteDword( &ac->PCStats->unknown10);
990 stream->WriteDword( &ac->PCStats->KillsChapterXP);
991 stream->WriteDword( &ac->PCStats->KillsChapterCount);
992 stream->WriteDword( &ac->PCStats->KillsTotalXP);
993 stream->WriteDword( &ac->PCStats->KillsTotalCount);
994 for (i=0;i<4;i++) {
995 stream->WriteResRef( ac->PCStats->FavouriteSpells[i]);
997 for (i=0;i<4;i++) {
998 stream->WriteWord( &ac->PCStats->FavouriteSpellsCount[i]);
1000 for (i=0;i<4;i++) {
1001 stream->WriteResRef( ac->PCStats->FavouriteWeapons[i]);
1003 for (i=0;i<4;i++) {
1004 stream->WriteWord( &ac->PCStats->FavouriteWeaponsCount[i]);
1006 stream->Write( ac->PCStats->SoundSet, 8); //soundset
1007 if (core->HasFeature(GF_SOUNDFOLDERS) ) {
1008 stream->Write(ac->PCStats->SoundFolder, 32);
1010 if (version==GAM_VER_IWD2 || version==GAM_VER_GEMRB) {
1011 stream->Write(filling, 194);
1013 return 0;
1016 int GAMImporter::PutPCs(DataStream *stream, Game *game)
1018 unsigned int i;
1019 PluginHolder<ActorMgr> am(IE_CRE_CLASS_ID);
1020 ieDword CREOffset = PCOffset + PCCount * PCSize;
1022 for(i=0;i<PCCount;i++) {
1023 assert(stream->GetPos() == PCOffset + i * PCSize);
1024 Actor *ac = game->GetPC(i, false);
1025 ieDword CRESize = am->GetStoredFileSize(ac);
1026 PutActor(stream, ac, CRESize, CREOffset, game->version);
1027 CREOffset += CRESize;
1029 assert(stream->GetPos() == PCOffset + PCCount * PCSize);
1031 CREOffset = PCOffset + PCCount * PCSize; // just for the asserts..
1032 for(i=0;i<PCCount;i++) {
1033 assert(stream->GetPos() == CREOffset);
1034 Actor *ac = game->GetPC(i, false);
1035 //reconstructing offsets again
1036 CREOffset += am->GetStoredFileSize(ac);
1037 am->PutActor( stream, ac);
1039 return 0;
1042 int GAMImporter::PutNPCs(DataStream *stream, Game *game)
1044 unsigned int i;
1045 PluginHolder<ActorMgr> am(IE_CRE_CLASS_ID);
1046 ieDword CREOffset = NPCOffset + NPCCount * PCSize;
1048 for(i=0;i<NPCCount;i++) {
1049 Actor *ac = game->GetNPC(i);
1050 ieDword CRESize = am->GetStoredFileSize(ac);
1051 PutActor(stream, ac, CRESize, CREOffset, game->version);
1052 CREOffset += CRESize;
1054 for(i=0;i<NPCCount;i++) {
1055 Actor *ac = game->GetNPC(i);
1056 //reconstructing offsets again
1057 am->GetStoredFileSize(ac);
1058 am->PutActor( stream, ac);
1060 return 0;
1063 int GAMImporter::PutMaze(DataStream *stream, Game *game)
1065 stream->Write( game->mazedata, MAZE_DATA_SIZE);
1066 return 0;
1069 int GAMImporter::PutFamiliars(DataStream *stream, Game *game)
1071 int len = 0;
1072 if (core->GetBeastsINI()) {
1073 len = BESTIARY_SIZE;
1074 if (game->version==GAM_VER_PST) {
1075 //only GemRB version can have all features, return when it is PST
1076 //gemrb version will have the beasts after the familiars
1077 stream->Write( game->beasts, len );
1078 return 0;
1082 char filling[FAMILIAR_FILL_SIZE];
1084 memset( filling,0,sizeof(filling) );
1085 for (unsigned int i=0;i<9;i++) {
1086 stream->WriteResRef( game->GetFamiliar(i) );
1088 stream->WriteDword( &SavedLocOffset);
1089 if (len) {
1090 stream->Write( game->beasts, len );
1092 stream->Write( filling, FAMILIAR_FILL_SIZE - len);
1093 return 0;
1096 int GAMImporter::PutGame(DataStream *stream, Game *game)
1098 int ret;
1100 if (!stream || !game) {
1101 return -1;
1104 ret = PutHeader( stream, game);
1105 if (ret) {
1106 return ret;
1109 ret = PutPCs( stream, game);
1110 if (ret) {
1111 return ret;
1114 ret = PutNPCs( stream, game);
1115 if (ret) {
1116 return ret;
1119 if (game->mazedata) {
1120 ret = PutMaze( stream, game);
1121 if (ret) {
1122 return ret;
1126 ret = PutVariables( stream, game);
1127 if (ret) {
1128 return ret;
1131 ret = PutJournals( stream, game);
1132 if (ret) {
1133 return ret;
1136 if (core->HasFeature(GF_HAS_KAPUTZ) ) {
1137 ret = PutKillVars( stream, game);
1138 if (ret) {
1139 return ret;
1143 if (FamiliarsOffset) {
1144 ret = PutFamiliars( stream, game);
1145 if (ret) {
1146 return ret;
1149 if (SavedLocOffset) {
1150 ret = PutSavedLocations( stream, game);
1151 if (ret) {
1152 return ret;
1155 if (PPLocOffset) {
1156 ret = PutPlaneLocations( stream, game);
1157 if (ret) {
1158 return ret;
1162 return 0;
1165 #include "plugindef.h"
1167 GEMRB_PLUGIN(0xD7F7040, "GAM File Importer")
1168 PLUGIN_CLASS(IE_GAM_CLASS_ID, GAMImporter)
1169 END_PLUGIN()