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"
26 #include "DataFileMgr.h"
28 #include "Interface.h"
30 #include "System/MemoryStream.h"
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)
45 GAMImporter::~GAMImporter(void)
47 if (str
&& autoFree
) {
52 bool GAMImporter::Open(DataStream
* stream
, bool autoFree
)
61 this->autoFree
= autoFree
;
63 str
->Read( Signature
, 8 );
64 if (strncmp( Signature
, "GAMEV0.0", 8 ) == 0) {
65 version
= GAM_VER_GEMRB
;
67 } else if (strncmp( Signature
, "GAMEV2.0", 8 ) == 0) {
68 //soa (soa part of tob)
69 version
= GAM_VER_BG2
;
71 } else if (strncmp( Signature
, "GAMEV2.1", 8 ) == 0) {
73 version
= GAM_VER_TOB
;
75 } else if (strncmp( Signature
, "GAMEV1.0", 8 ) == 0) {
79 } else if (strncmp( Signature
, "GAMEV2.2", 8 ) == 0) {
81 version
= GAM_VER_IWD2
;
83 } else if (strncmp( Signature
, "GAMEV1.1", 8 ) == 0) {
85 if (core
->HasFeature(GF_HAS_KAPUTZ
) ) { //pst
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
) ) {
92 version
= GAM_VER_IWD
;
98 printMessage( "GAMImporter","This file is not a valid GAM File\n", LIGHT_RED
);
105 Game
* GAMImporter::LoadGame(Game
*newGame
, int ver_override
)
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
114 newGame
->version
= ver_override
;
117 newGame
->version
= version
;
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
] );
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
);
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
);
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
181 str
->Seek( 64, GEM_CURRENT_POS
);
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
);
195 const char* resref
= tm
->QueryField( playmode
);
196 strnlwrcpy( newGame
->CurrentArea
, resref
, 8 );
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 );
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
224 str
->Seek( GlobalOffset
, GEM_STREAM_START
);
225 for (i
= 0; i
< GlobalCount
; i
++) {
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
++) {
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
) {
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
);
266 if (FamiliarsOffset
) {
267 str
->Seek( FamiliarsOffset
, GEM_STREAM_START
);
269 str
->ReadResRef( newGame
->GetFamiliar(i
) );
272 //clear these fields up
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
) {
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
);
302 if (PPLocCount
&& PPLocOffset
) {
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
);
318 void SanityCheck(ieWord a
,ieWord
&b
,const char *message
)
325 printMessage("GAMImporter"," ",LIGHT_RED
);
326 printf("Invalid Slot Enabler caught: %s!\n", message
);
331 Actor
* GAMImporter::GetActor(Holder
<ActorMgr
> aM
, bool is_in_party
)
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
) {
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
) {
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
++) {
393 if ((tmp
[0]!=0) && (pcInfo
.QuickSpellResRef
[0]==0)) {
394 memcpy( pcInfo
.QuickSpellResRef
[i
], tmp
, 8);
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
++) {
403 if ((tmp
[0]!=0) && (pcInfo
.QuickSpellResRef
[0]==0)) {
404 memcpy( pcInfo
.QuickSpellResRef
[i
], tmp
, 8);
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
;
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
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();
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 );
467 aM
->Open( ms
, true );
468 actor
= aM
->GetActor(tmpWord
);
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
;
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
483 aM
->Open( ds
, true );
484 actor
= aM
->GetActor(pcInfo
.PartyOrder
);
492 str
->Seek(pos
, GEM_STREAM_START
);
494 actor
->CreateStats();
495 PCStatsStruct
*ps
= actor
->PCStats
;
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
;
518 void GAMImporter::GetPCStats (PCStatsStruct
*ps
)
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
565 int GAMImporter::GetStoredFileSize(Game
*game
)
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();
577 switch(game
->version
)
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
;
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
;
640 headersize
+= KillVarsCount
* 84;
643 if (game
->version
==GAM_VER_BG
) {
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
683 int GAMImporter::PutSavedLocations(DataStream
*stream
, Game
*game
)
687 for (unsigned int i
=0;i
<SavedLocCount
;i
++) {
688 GAMLocationEntry
*j
= game
->GetSavedLocationEntry(i
);
690 stream
->WriteResRef(j
->AreaResRef
);
692 stream
->WriteWord(&tmpWord
);
694 stream
->WriteWord(&tmpWord
);
699 int GAMImporter::PutPlaneLocations(DataStream
*stream
, Game
*game
)
703 for (unsigned int i
=0;i
<PPLocCount
;i
++) {
704 GAMLocationEntry
*j
= game
->GetPlaneLocationEntry(i
);
706 stream
->WriteResRef(j
->AreaResRef
);
708 stream
->WriteWord(&tmpWord
);
710 stream
->WriteWord(&tmpWord
);
716 int GAMImporter::PutKillVars(DataStream
*stream
, Game
*game
)
720 Variables::iterator pos
=NULL
;
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);
738 int GAMImporter::PutVariables(DataStream
*stream
, Game
*game
)
742 Variables::iterator pos
=NULL
;
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);
760 int GAMImporter::PutHeader(DataStream
*stream
, Game
*game
)
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
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);
784 stream
->WriteWord( &game
->WhichFormation
);
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
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
) {
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
);
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
835 stream
->WriteDword( &game
->RealTime
); //this isn't correct, this field is the realtime
836 stream
->WriteDword( &PPLocOffset
);
837 stream
->WriteDword( &PPLocCount
);
839 memset( filling
, 0, sizeof(filling
) );
840 stream
->Write( &filling
, 52); //unknown
844 int GAMImporter::PutActor(DataStream
*stream
, Actor
*ac
, ieDword CRESize
, ieDword CREOffset
, ieDword version
)
851 memset(filling
,0,sizeof(filling
) );
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
);
872 stream
->WriteWord( &tmpWord
);
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
);
885 stream
->Write(filling
, 96);
888 if (version
==GAM_VER_IWD2
|| version
==GAM_VER_GEMRB
) {
890 stream
->WriteWord( ac
->PCStats
->QuickWeaponSlots
+i
);
891 stream
->WriteWord( ac
->PCStats
->QuickWeaponSlots
+4+i
);
894 stream
->WriteWord( ac
->PCStats
->QuickWeaponHeaders
+i
);
895 stream
->WriteWord( ac
->PCStats
->QuickWeaponHeaders
+4+i
);
899 stream
->WriteWord( ac
->PCStats
->QuickWeaponSlots
+i
);
902 stream
->WriteWord( ac
->PCStats
->QuickWeaponHeaders
+i
);
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);
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) {
925 stream
->Write(filling
,10);
926 memset(filling
,0,sizeof(filling
) );
929 stream
->Write(ac
->PCStats
->QuickSpells
[i
],8);
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
);
945 stream
->WriteWord(ac
->PCStats
->QuickItemSlots
+i
);
948 stream
->WriteWord(ac
->PCStats
->QuickItemHeaders
+i
);
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);
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);
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);
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
);
995 stream
->WriteResRef( ac
->PCStats
->FavouriteSpells
[i
]);
998 stream
->WriteWord( &ac
->PCStats
->FavouriteSpellsCount
[i
]);
1001 stream
->WriteResRef( ac
->PCStats
->FavouriteWeapons
[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);
1016 int GAMImporter::PutPCs(DataStream
*stream
, Game
*game
)
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
);
1042 int GAMImporter::PutNPCs(DataStream
*stream
, Game
*game
)
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
);
1063 int GAMImporter::PutMaze(DataStream
*stream
, Game
*game
)
1065 stream
->Write( game
->mazedata
, MAZE_DATA_SIZE
);
1069 int GAMImporter::PutFamiliars(DataStream
*stream
, Game
*game
)
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
);
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
);
1090 stream
->Write( game
->beasts
, len
);
1092 stream
->Write( filling
, FAMILIAR_FILL_SIZE
- len
);
1096 int GAMImporter::PutGame(DataStream
*stream
, Game
*game
)
1100 if (!stream
|| !game
) {
1104 ret
= PutHeader( stream
, game
);
1109 ret
= PutPCs( stream
, game
);
1114 ret
= PutNPCs( stream
, game
);
1119 if (game
->mazedata
) {
1120 ret
= PutMaze( stream
, game
);
1126 ret
= PutVariables( stream
, game
);
1131 ret
= PutJournals( stream
, game
);
1136 if (core
->HasFeature(GF_HAS_KAPUTZ
) ) {
1137 ret
= PutKillVars( stream
, game
);
1143 if (FamiliarsOffset
) {
1144 ret
= PutFamiliars( stream
, game
);
1149 if (SavedLocOffset
) {
1150 ret
= PutSavedLocations( stream
, game
);
1156 ret
= PutPlaneLocations( stream
, game
);
1165 #include "plugindef.h"
1167 GEMRB_PLUGIN(0xD7F7040, "GAM File Importer")
1168 PLUGIN_CLASS(IE_GAM_CLASS_ID
, GAMImporter
)