Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/network64/src/network/packet/Gamestate.cc @ 2309

Last change on this file since 2309 was 2309, checked in by scheusso, 15 years ago

made some adjustments mostly to the networkid (classid) in order to have it platform independent

  • Property svn:eol-style set to native
File size: 19.1 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Oliver Scheuss, (C) 2008
24 *   Co-authors:
25 *      ...
26 *
27 */
28
29#include "Gamestate.h"
30#include "network/ClientInformation.h"
31#include "network/GamestateHandler.h"
32#include "core/CoreIncludes.h"
33#include "core/Iterator.h"
34
35#include <zlib.h>
36#include <assert.h>
37
38
39
40namespace orxonox {
41
42namespace packet {
43
44#define GAMESTATE_START(data) (data + sizeof(GamestateHeader))
45#define GAMESTATE_HEADER(data) ((GamestateHeader *)data)
46#define HEADER GAMESTATE_HEADER(data_)
47
48
49#define PACKET_FLAG_GAMESTATE  ENET_PACKET_FLAG_RELIABLE
50
51Gamestate::Gamestate()
52{
53  flags_ = flags_ | PACKET_FLAG_GAMESTATE;
54}
55
56Gamestate::Gamestate(uint8_t *data, unsigned int clientID):
57    Packet(data, clientID)
58{
59  flags_ = flags_ | PACKET_FLAG_GAMESTATE;
60}
61
62Gamestate::Gamestate(uint8_t *data)
63{
64  flags_ = flags_ | PACKET_FLAG_GAMESTATE;
65  data_=data;
66}
67
68
69Gamestate::~Gamestate()
70{
71}
72
73bool Gamestate::collectData(int id, uint8_t mode)
74{
75  uint32_t tempsize=0, currentsize=0;
76  assert(data_==0);
77  uint32_t size = calcGamestateSize(id, mode);
78
79  COUT(4) << "G.ST.Man: producing gamestate with id: " << id << std::endl;
80  if(size==0)
81    return false;
82  data_ = new unsigned char[size + sizeof(GamestateHeader)];
83  if(!data_){
84    COUT(2) << "GameStateManager: could not allocate memory" << std::endl;
85    return false;
86  }
87
88  //start collect data synchronisable by synchronisable
89  uint8_t *mem=data_;
90  mem+=sizeof(GamestateHeader);
91  ObjectList<Synchronisable>::iterator it;
92  for(it = ObjectList<Synchronisable>::begin(); it; ++it){
93    tempsize=it->getSize(id, mode);
94
95    if(currentsize+tempsize > size){
96      assert(0); // if we don't use multithreading this part shouldn't be neccessary
97      // start allocate additional memory
98      COUT(3) << "G.St.Man: need additional memory" << std::endl;
99      ObjectList<Synchronisable>::iterator temp = it;
100      int addsize=tempsize;
101      while(++temp)
102        addsize+=temp->getSize(id, mode);
103      data_ = (uint8_t *)realloc(data_, sizeof(GamestateHeader) + currentsize + addsize);
104      if(!data_)
105        return false;
106      size = currentsize+addsize;
107    }// stop allocate additional memory
108
109
110    //if(it->doSelection(id))
111    dataMap_[mem-data_]=(*it);  // save the mem location of the synchronisable data
112    if(!it->getData(mem, id, mode))
113      return false; // mem pointer gets automatically increased because of call by reference
114    // increase size counter by size of current synchronisable
115    currentsize+=tempsize;
116  }
117
118
119  //start write gamestate header
120  HEADER->packetType = ENUM::Gamestate;
121  HEADER->datasize = currentsize;
122  HEADER->id = id;
123  HEADER->diffed = false;
124  HEADER->complete = true;
125  HEADER->compressed = false;
126  //stop write gamestate header
127
128  COUT(5) << "G.ST.Man: Gamestate size: " << currentsize << std::endl;
129  COUT(5) << "G.ST.Man: 'estimated' (and corrected) Gamestate size: " << size << std::endl;
130  return true;
131}
132
133bool Gamestate::spreadData(uint8_t mode)
134{
135  assert(data_);
136  assert(!HEADER->compressed);
137  assert(!HEADER->diffed);
138  uint8_t *mem=data_+sizeof(GamestateHeader);
139    // get the start of the Synchronisable list
140  //ObjectList<Synchronisable>::iterator it=ObjectList<Synchronisable>::begin();
141  Synchronisable *s;
142
143  // update the data of the objects we received
144  while(mem < data_+sizeof(GamestateHeader)+HEADER->datasize){
145    synchronisableHeader *objectheader = (synchronisableHeader*)mem;
146
147    s = Synchronisable::getSynchronisable( objectheader->objectID );
148    if(!s)
149    {
150      Synchronisable::fabricate(mem, mode);
151    }
152    else
153    {
154      bool b = s->updateData(mem, mode);
155      assert(b);
156    }
157  }
158
159  return true;
160}
161
162
163
164int Gamestate::getID(){
165  return HEADER->id;
166}
167
168uint32_t Gamestate::getSize() const
169{
170  assert(data_);
171  if(HEADER->compressed)
172    return HEADER->compsize+sizeof(GamestateHeader);
173  else
174  {
175    return HEADER->datasize+sizeof(GamestateHeader);
176  }
177}
178
179bool Gamestate::operator==(packet::Gamestate gs){
180  uint8_t *d1 = data_+sizeof(GamestateHeader);
181  uint8_t *d2 = gs.data_+sizeof(GamestateHeader);
182  assert(!isCompressed());
183  assert(!gs.isCompressed());
184  while(d1<data_+HEADER->datasize)
185  {
186    if(*d1!=*d2)
187      return false;
188    d1++;
189    d2++;
190  }
191  return true;
192}
193
194bool Gamestate::process()
195{
196  return GamestateHandler::addGamestate(this, getClientID());
197}
198
199
200
201bool Gamestate::compressData()
202{
203  assert(HEADER);
204  assert(!HEADER->compressed);
205  uLongf buffer = (uLongf)(((HEADER->datasize + 12)*1.01)+1);
206  if(buffer==0)
207    return false;
208
209  uint8_t *ndata = new uint8_t[buffer+sizeof(GamestateHeader)];
210  uint8_t *dest = GAMESTATE_START(ndata);
211  //unsigned char *dest = new unsigned char[buffer];
212  uint8_t *source = GAMESTATE_START(data_);
213  int retval;
214  retval = compress( dest, &buffer, source, (uLong)(HEADER->datasize) );
215  switch ( retval ) {
216    case Z_OK: COUT(5) << "G.St.Man: compress: successfully compressed" << std::endl; break;
217    case Z_MEM_ERROR: COUT(1) << "G.St.Man: compress: not enough memory available in gamestate.compress" << std::endl; return false;
218    case Z_BUF_ERROR: COUT(2) << "G.St.Man: compress: not enough memory available in the buffer in gamestate.compress" << std::endl; return false;
219    case Z_DATA_ERROR: COUT(2) << "G.St.Man: compress: data corrupted in gamestate.compress" << std::endl; return false;
220  }
221#ifndef NDEBUG
222  //decompress and compare the start and the decompressed data
223  uint8_t *rdata = new uint8_t[HEADER->datasize+sizeof(GamestateHeader)];
224  uint8_t *d2 = GAMESTATE_START(rdata);
225  uLongf length2 = HEADER->datasize;
226  uncompress(d2, &length2, dest, buffer);
227  for(unsigned int i=0; i<HEADER->datasize; i++){
228    assert(*(source+i)==*(d2+i));
229  }
230  delete[] rdata;
231#endif
232
233  //copy and modify header
234#ifndef NDEBUG
235  HEADER->crc32 = calcCRC(data_+sizeof(GamestateHeader), HEADER->datasize);
236#endif
237  *GAMESTATE_HEADER(ndata) = *HEADER;
238  //delete old data
239  delete[] data_;
240  //save new data
241  data_ = ndata;
242  HEADER->compsize = buffer;
243  HEADER->compressed = true;
244  assert(HEADER->compressed);
245  COUT(4) << "gamestate compress datasize: " << HEADER->datasize << " compsize: " << HEADER->compsize << std::endl;
246  return true;
247}
248bool Gamestate::decompressData()
249{
250  assert(HEADER);
251  assert(HEADER->compressed);
252  COUT(4) << "GameStateClient: uncompressing gamestate. id: " << HEADER->id << ", baseid: " << HEADER->base_id << ", datasize: " << HEADER->datasize << ", compsize: " << HEADER->compsize << std::endl;
253  uint32_t datasize = HEADER->datasize;
254  uint32_t compsize = HEADER->compsize;
255  uint32_t bufsize;
256//  assert(compsize<=datasize);
257  bufsize = datasize;
258  assert(bufsize!=0);
259  uint8_t *ndata = new uint8_t[bufsize + sizeof(GamestateHeader)];
260  uint8_t *dest = ndata + sizeof(GamestateHeader);
261  uint8_t *source = data_ + sizeof(GamestateHeader);
262  int retval;
263  uLongf length=bufsize;
264  retval = uncompress( dest, &length, source, (uLong)compsize );
265  switch ( retval ) {
266    case Z_OK: COUT(5) << "successfully decompressed" << std::endl; break;
267    case Z_MEM_ERROR: COUT(1) << "not enough memory available" << std::endl; return false;
268    case Z_BUF_ERROR: COUT(2) << "not enough memory available in the buffer" << std::endl; return false;
269    case Z_DATA_ERROR: COUT(2) << "data corrupted (zlib)" << std::endl; return false;
270  }
271#ifndef NDEBUG
272  assert(HEADER->crc32==calcCRC(ndata+sizeof(GamestateHeader), HEADER->datasize));
273#endif
274
275  //copy over the header
276  *GAMESTATE_HEADER(ndata) = *HEADER;
277
278  if (this->bDataENetAllocated_){
279    // Memory was allocated by ENet. --> We let it be since enet_packet_destroy will
280    // deallocated it anyway. So data and packet stay together.
281    this->bDataENetAllocated_ = false;
282  }
283  else{
284    // We allocated the memory in the first place (unlikely). So we destroy the old data
285    // and overwrite it with the new decompressed data.
286    delete[] this->data_;
287  }
288
289  //set new pointers
290  data_ = ndata;
291  HEADER->compressed = false;
292  assert(HEADER->datasize==datasize);
293  assert(HEADER->compsize==compsize);
294  return true;
295}
296
297Gamestate *Gamestate::diff(Gamestate *base)
298{
299  assert(HEADER);
300  assert(!HEADER->compressed);
301  assert(!HEADER->diffed);
302  //unsigned char *basep = base->getGs()/*, *gs = getGs()*/;
303  uint8_t *basep = GAMESTATE_START(base->data_), *gs = GAMESTATE_START(this->data_);
304  uint32_t of=0; // pointers offset
305  uint32_t dest_length=0;
306  dest_length=HEADER->datasize;
307  if(dest_length==0)
308    return NULL;
309  uint8_t *ndata = new uint8_t[dest_length*sizeof(uint8_t)+sizeof(GamestateHeader)];
310  uint8_t *dest = ndata + sizeof(GamestateHeader);
311  while(of < GAMESTATE_HEADER(base->data_)->datasize && of < HEADER->datasize){
312    *(dest+of)=*(basep+of)^*(gs+of); // do the xor
313    ++of;
314  }
315  if(GAMESTATE_HEADER(base->data_)->datasize!=HEADER->datasize){
316    uint8_t n=0;
317    if(GAMESTATE_HEADER(base->data_)->datasize < HEADER->datasize){
318      while(of<dest_length){
319        *(dest+of)=n^*(gs+of);
320        of++;
321      }
322    }
323  }
324
325  *GAMESTATE_HEADER(ndata) = *HEADER;
326  GAMESTATE_HEADER(ndata)->diffed = true;
327  GAMESTATE_HEADER(ndata)->base_id = base->getID();
328  Gamestate *g = new Gamestate(ndata, getClientID());
329  g->flags_=flags_;
330  g->packetDirection_ = packetDirection_;
331  return g;
332}
333
334Gamestate* Gamestate::doSelection(unsigned int clientID){
335  assert(data_);
336  std::map<uint32_t, Synchronisable *>::iterator it;
337
338  // allocate memory for new data
339  uint8_t *gdata = new uint8_t[HEADER->datasize+sizeof(GamestateHeader)];
340  // create a gamestate out of it
341  Gamestate *gs = new Gamestate(gdata);
342  uint8_t *newdata = gdata + sizeof(GamestateHeader);
343  uint8_t *origdata = GAMESTATE_START(data_);
344
345  //copy the GamestateHeader
346  *(GamestateHeader*)gdata = *HEADER;
347
348  synchronisableHeader *oldobjectheader, *newobjectheader;
349  uint32_t objectOffset;
350
351  //copy in the zeros
352  for(it=dataMap_.begin(); it!=dataMap_.end(); it++){
353    oldobjectheader = (synchronisableHeader*)origdata;
354    newobjectheader = (synchronisableHeader*)newdata;
355    uint32_t objectsize = oldobjectheader->size;
356    assert(it->second->objectID==oldobjectheader->objectID);
357    *newobjectheader = *oldobjectheader;
358    objectOffset=sizeof(synchronisableHeader); //skip the size and the availableData variables in the objectheader
359    if(it->second->doSelection(HEADER->id)){
360      assert(newobjectheader->dataAvailable==true);
361      memcpy(newdata+objectOffset, origdata+objectOffset, objectsize-objectOffset);
362    }else{
363      newobjectheader->dataAvailable=false;
364      memset(newdata+objectOffset, 0, objectsize-objectOffset);
365      assert(objectOffset==objectsize);
366    }
367    newdata += objectsize;
368    origdata += objectsize;
369  }
370  return gs;
371}
372
373
374Gamestate* Gamestate::intelligentDiff(Gamestate *base, unsigned int clientID){
375  // asserts
376  assert(data_);
377  assert(base->data_);
378  assert(!GAMESTATE_HEADER(base->data_)->diffed);
379  assert(!GAMESTATE_HEADER(base->data_)->compressed);
380  assert(!HEADER->compressed);
381  assert(!HEADER->diffed);
382
383  //preparations
384  std::map<uint32_t, Synchronisable *>::iterator it;
385  uint8_t *origdata, *basedata, *destdata, *ndata;
386  uint32_t objectOffset, streamOffset=0;    //data offset
387  uint32_t minsize = (HEADER->datasize < GAMESTATE_HEADER(base->data_)->datasize) ? HEADER->datasize : GAMESTATE_HEADER(base->data_)->datasize;
388  synchronisableHeader *origheader;
389  synchronisableHeader *destheader;
390
391  origdata = GAMESTATE_START(this->data_);
392  basedata = GAMESTATE_START(base->data_);
393  ndata = new uint8_t[HEADER->datasize + sizeof(GamestateHeader)];
394  destdata = ndata + sizeof(GamestateHeader);
395
396  // do the diff
397  for(it=dataMap_.begin(); it!=dataMap_.end(); it++){
398    assert(streamOffset<HEADER->datasize);
399    bool sendData = it->second->doSelection(HEADER->id);
400    origheader = (synchronisableHeader *)(origdata+streamOffset);
401    destheader = (synchronisableHeader *)(destdata+streamOffset);
402
403    //copy and partially diff the object header
404    assert(sizeof(synchronisableHeader)==3*sizeof(uint32_t)+sizeof(bool));
405    *(uint32_t*)destdata = *(uint32_t*)origdata; //size (do not diff)
406    *(bool*)(destdata+sizeof(uint32_t)) = sendData;
407    if(sendData){
408      *(uint32_t*)(destdata+sizeof(uint32_t)+sizeof(bool)) = *(uint32_t*)(basedata+sizeof(uint32_t)+sizeof(bool)) ^ *(uint32_t*)(origdata+sizeof(uint32_t)+sizeof(bool)); //objectid (diff it)
409      *(uint32_t*)(destdata+2*sizeof(uint32_t)+sizeof(bool)) = *(uint32_t*)(basedata+2*sizeof(uint32_t)+sizeof(bool)) ^ *(uint32_t*)(origdata+2*sizeof(uint32_t)+sizeof(bool)); //classid (diff it)
410    }else{
411      *(uint32_t*)(destdata+sizeof(uint32_t)+sizeof(bool)) = 0;
412      *(uint32_t*)(destdata+2*sizeof(uint32_t)+sizeof(bool)) = 0;
413    }
414    objectOffset=sizeof(synchronisableHeader);
415    streamOffset+=sizeof(synchronisableHeader);
416
417    //now handle the object data or fill with zeros
418    while(objectOffset<origheader->size ){
419
420      if(sendData && streamOffset<minsize)
421        *(destdata+objectOffset)=*(basedata+objectOffset)^*(origdata+objectOffset); // do the xor
422      else if(sendData)
423        *(destdata+objectOffset)=((uint8_t)0)^*(origdata+objectOffset); // xor with 0 (basestream is too short)
424      else
425        *(destdata+objectOffset)=0; // set to 0 because this object should not be transfered
426
427      objectOffset++;
428      streamOffset++;
429    }
430    destdata+=objectOffset;
431    origdata+=objectOffset;
432    basedata+=objectOffset;
433  }
434
435  //copy over the gamestate header and set the diffed flag
436  *(GamestateHeader *)ndata = *HEADER; //copy over the header
437  Gamestate *gs = new Gamestate(ndata);
438  GAMESTATE_HEADER(ndata)->diffed=true;
439  return gs;
440}
441
442Gamestate* Gamestate::intelligentUnDiff(Gamestate *base){
443  // asserts
444  assert(data_);
445  assert(base->data_);
446  assert(!GAMESTATE_HEADER(base->data_)->diffed);
447  assert(!GAMESTATE_HEADER(base->data_)->compressed);
448  assert(!HEADER->compressed);
449  assert(HEADER->diffed);
450
451  //preparations
452  std::map<uint32_t, Synchronisable *>::iterator it;
453  uint8_t *origdata, *basedata, *destdata, *ndata;
454  uint32_t objectOffset, streamOffset=0;    //data offset
455  uint32_t minsize = (HEADER->datasize < GAMESTATE_HEADER(base->data_)->datasize) ? HEADER->datasize : GAMESTATE_HEADER(base->data_)->datasize;
456  synchronisableHeader *origheader;
457  synchronisableHeader *destheader;
458
459  origdata = GAMESTATE_START(this->data_);
460  basedata = GAMESTATE_START(base->data_);
461  ndata = new uint8_t[HEADER->datasize + sizeof(GamestateHeader)];
462  destdata = ndata + sizeof(GamestateHeader);
463
464  // do the undiff
465  for(it=dataMap_.begin(); it!=dataMap_.end(); it++){
466    assert(streamOffset<HEADER->datasize);
467    origheader = (synchronisableHeader *)(origdata+streamOffset);
468    destheader = (synchronisableHeader *)(destdata+streamOffset);
469    bool sendData;
470
471    //copy and partially diff the object header
472    assert(sizeof(synchronisableHeader)==3*sizeof(uint32_t)+sizeof(bool));
473    *(uint32_t*)destdata = *(uint32_t*)origdata; //size (do not diff)
474    *(bool*)(destdata+sizeof(uint32_t)) = *(bool*)(origdata+sizeof(uint32_t));
475    sendData = *(bool*)(origdata+sizeof(uint32_t));
476    if(sendData){
477      *(uint32_t*)(destdata+sizeof(uint32_t)+sizeof(bool)) = *(uint32_t*)(basedata+sizeof(uint32_t)+sizeof(bool)) ^ *(uint32_t*)(origdata+sizeof(uint32_t)+sizeof(bool)); //objectid (diff it)
478      *(uint32_t*)(destdata+2*sizeof(uint32_t)+sizeof(bool)) = *(uint32_t*)(basedata+2*sizeof(uint32_t)+sizeof(bool)) ^ *(uint32_t*)(origdata+2*sizeof(uint32_t)+sizeof(bool)); //classid (diff it)
479    }else{
480      *(uint32_t*)(destdata+sizeof(uint32_t)+sizeof(bool)) = 0;
481      *(uint32_t*)(destdata+2*sizeof(uint32_t)+sizeof(bool)) = 0;
482    }
483    objectOffset=sizeof(synchronisableHeader);
484    streamOffset+=sizeof(synchronisableHeader);
485
486    //now handle the object data or fill with zeros
487    while(objectOffset<origheader->size ){
488
489      if(sendData && streamOffset<minsize)
490        *(destdata+objectOffset)=*(basedata+objectOffset)^*(origdata+objectOffset); // do the xor
491      else if(sendData)
492        *(destdata+objectOffset)=((unsigned char)0)^*(origdata+objectOffset); // xor with 0 (basestream is too short)
493      else
494        *(destdata+objectOffset)=0; // set to 0 because this object should not be transfered
495
496      objectOffset++;
497      streamOffset++;
498    }
499    destdata+=objectOffset;
500    origdata+=objectOffset;
501    basedata+=objectOffset;
502  }
503
504  //copy over the gamestate header and set the diffed flag
505  *(GamestateHeader *)ndata = *HEADER; //copy over the header
506  Gamestate *gs = new Gamestate(ndata);
507  GAMESTATE_HEADER(ndata)->diffed=false;
508  return gs;
509}
510
511Gamestate *Gamestate::undiff(Gamestate *base)
512{
513  assert(this && base);assert(HEADER);
514  assert(HEADER->diffed);
515  assert(!HEADER->compressed && !GAMESTATE_HEADER(base->data_)->compressed);
516  //unsigned char *basep = base->getGs()/*, *gs = getGs()*/;
517  uint8_t *basep = GAMESTATE_START(base->data_);
518  uint8_t *gs = GAMESTATE_START(this->data_);
519  uint32_t of=0; // pointers offset
520  uint32_t dest_length=0;
521  dest_length=HEADER->datasize;
522  if(dest_length==0)
523    return NULL;
524  uint8_t *ndata = new uint8_t[dest_length*sizeof(uint8_t)+sizeof(GamestateHeader)];
525  uint8_t *dest = ndata + sizeof(GamestateHeader);
526  while(of < GAMESTATE_HEADER(base->data_)->datasize && of < HEADER->datasize){
527    *(dest+of)=*(basep+of)^*(gs+of); // do the xor
528    ++of;
529  }
530  if(GAMESTATE_HEADER(base->data_)->datasize!=HEADER->datasize){
531    uint8_t n=0;
532    if(GAMESTATE_HEADER(base->data_)->datasize < HEADER->datasize){
533      while(of < dest_length){
534        *(dest+of)=n^*(gs+of);
535        of++;
536      }
537    }
538  }
539  *GAMESTATE_HEADER(ndata) = *HEADER;
540  GAMESTATE_HEADER(ndata)->diffed = false;
541  Gamestate *g = new Gamestate(ndata, getClientID());
542  g->flags_=flags_;
543  g->packetDirection_ = packetDirection_;
544  assert(!g->isDiffed());
545  assert(!g->isCompressed());
546  return g;
547}
548
549
550uint32_t Gamestate::calcGamestateSize(int32_t id, uint8_t mode)
551{
552  uint32_t size=0;
553    // get the start of the Synchronisable list
554  ObjectList<Synchronisable>::iterator it;
555    // get total size of gamestate
556  for(it = ObjectList<Synchronisable>::begin(); it; ++it)
557    size+=it->getSize(id, mode); // size of the actual data of the synchronisable
558//  size+=sizeof(GamestateHeader);
559  return size;
560}
561
562/**
563 * This function removes a Synchronisable out of the universe
564 * @param it iterator of the list pointing to the object
565 * @return iterator pointing to the next object in the list
566 */
567  void Gamestate::removeObject(ObjectList<Synchronisable>::iterator &it) {
568    ObjectList<Synchronisable>::iterator temp=it;
569    ++it;
570    delete  *temp;
571  }
572
573  bool Gamestate::isDiffed(){
574    return HEADER->diffed;
575  }
576
577  bool Gamestate::isCompressed(){
578    return HEADER->compressed;
579  }
580
581  int Gamestate::getBaseID(){
582    return HEADER->base_id;
583  }
584}
585
586}
Note: See TracBrowser for help on using the repository browser.