ULTIMA1: Redeveloped create_ultima to create individual bitmaps
This commit is contained in:
parent
260b8aaca5
commit
53a4380bef
17 changed files with 207 additions and 2837 deletions
|
@ -1,85 +0,0 @@
|
||||||
/* ScummVM - Graphic Adventure Engine
|
|
||||||
*
|
|
||||||
* ScummVM is the legal property of its developers, whose names
|
|
||||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
||||||
* file distributed with this source distribution.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Disable symbol overrides so that we can use system headers.
|
|
||||||
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
|
||||||
|
|
||||||
// HACK to allow building with the SDL backend on MinGW
|
|
||||||
// see bug #1800764 "TOOLS: MinGW tools building broken"
|
|
||||||
#ifdef main
|
|
||||||
#undef main
|
|
||||||
#endif // main
|
|
||||||
|
|
||||||
#include "archive.h"
|
|
||||||
#include "common/endian.h"
|
|
||||||
|
|
||||||
#define ARCHIVE_IDENT "SULT"
|
|
||||||
#define ARCHIVE_VERSION 1
|
|
||||||
|
|
||||||
void Archive::save() {
|
|
||||||
// Write identifying string and archive version
|
|
||||||
_file.write(ARCHIVE_IDENT, 4);
|
|
||||||
_file.writeWord(ARCHIVE_VERSION);
|
|
||||||
_file.writeWord(_index.size());
|
|
||||||
|
|
||||||
// Figure out the size the entire index needs to be (i.e. where the data starts)
|
|
||||||
size_t dataOffset = 8;
|
|
||||||
for (uint idx = 0; idx < _index.size(); ++idx)
|
|
||||||
dataOffset += _index[idx].getIndexSize();
|
|
||||||
dataOffset = ((dataOffset + 1) / 2) * 2;
|
|
||||||
|
|
||||||
// Iterate through writing out index entries
|
|
||||||
for (uint idx = 0; idx < _index.size(); ++idx) {
|
|
||||||
ArchiveEntry &ae = _index[idx];
|
|
||||||
ae._offset = dataOffset;
|
|
||||||
_file.writeString(ae._name.c_str());
|
|
||||||
_file.writeLong(ae._offset);
|
|
||||||
_file.writeWord(ae._size);
|
|
||||||
|
|
||||||
dataOffset += ae._size;
|
|
||||||
}
|
|
||||||
if (_file.pos() % 2)
|
|
||||||
_file.writeByte(0);
|
|
||||||
|
|
||||||
// Write out the contents of each resource
|
|
||||||
for (uint idx = 0; idx < _index.size(); ++idx) {
|
|
||||||
const ArchiveEntry &ae = _index[idx];
|
|
||||||
if (_file.pos() != ae._offset)
|
|
||||||
error("Incorrect offset");
|
|
||||||
_file.write(ae._data, ae._size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Archive::open(const Common::String &name) {
|
|
||||||
return _file.open(name.c_str(), Common::kFileWriteMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Archive::close() {
|
|
||||||
if (_file.isOpen()) {
|
|
||||||
save();
|
|
||||||
_file.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Archive::add(const Common::String &name, Common::MemFile &f) {
|
|
||||||
_index.push_back(ArchiveEntry(name, f.getData(), f.size()));
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
/* ScummVM - Graphic Adventure Engine
|
|
||||||
*
|
|
||||||
* ScummVM is the legal property of its developers, whose names
|
|
||||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
||||||
* file distributed with this source distribution.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef CC_H
|
|
||||||
#define CC_H
|
|
||||||
|
|
||||||
#include "file.h"
|
|
||||||
#include "str.h"
|
|
||||||
#include "common/array.h"
|
|
||||||
|
|
||||||
class Archive {
|
|
||||||
/**
|
|
||||||
* Details of a single entry in the archive
|
|
||||||
*/
|
|
||||||
struct ArchiveEntry {
|
|
||||||
Common::String _name;
|
|
||||||
size_t _offset;
|
|
||||||
uint16 _size;
|
|
||||||
byte _data[65536];
|
|
||||||
|
|
||||||
ArchiveEntry() : _offset(0), _size(0) {
|
|
||||||
memset(_data, 0, 65536);
|
|
||||||
}
|
|
||||||
ArchiveEntry(const Common::String &name, const byte *data, uint32 size) :
|
|
||||||
_offset(0), _name(name), _size(size) {
|
|
||||||
memcpy(_data, data, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the size needed for saving the entry in the archive index
|
|
||||||
*/
|
|
||||||
size_t getIndexSize() const { return 6 + _name.size() + 1; }
|
|
||||||
};
|
|
||||||
private:
|
|
||||||
Common::Array<ArchiveEntry> _index;
|
|
||||||
Common::File _file;
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* Generates the archive and saves it to file
|
|
||||||
*/
|
|
||||||
void save();
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Consstructor
|
|
||||||
*/
|
|
||||||
Archive() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destructor
|
|
||||||
*/
|
|
||||||
~Archive() {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the archive for access
|
|
||||||
*/
|
|
||||||
bool open(const Common::String &name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the archive
|
|
||||||
*/
|
|
||||||
void close();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an entry to the CC
|
|
||||||
*/
|
|
||||||
void add(const Common::String &name, Common::MemFile &f);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -32,26 +32,17 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "archive.h"
|
|
||||||
#include "ultima1_map.h"
|
|
||||||
#include "ultima1_resources.h"
|
#include "ultima1_resources.h"
|
||||||
|
|
||||||
#define VERSION_NUMBER 1
|
#define VERSION_NUMBER 1
|
||||||
|
|
||||||
void NORETURN_PRE error(const char *s, ...) {
|
void error(const char *s, ...) {
|
||||||
printf("%s\n", s);
|
printf("%s\n", s);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
Archive archive;
|
extractUltima1Resources();
|
||||||
if (!archive.open("ultima.dat")) {
|
|
||||||
error("Could not open output file");
|
|
||||||
}
|
|
||||||
|
|
||||||
writeUltima1Resources(archive);
|
|
||||||
writeUltima1EnhancedMap(archive);
|
|
||||||
|
|
||||||
archive.close();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,9 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef CONSTANTS_H
|
#ifndef CREATE_ULTIMA_H
|
||||||
#define CONSTANTS_H
|
#define CREATE_ULTIMA_H
|
||||||
|
|
||||||
#include "archive.h"
|
extern void error(const char *s, ...);
|
||||||
|
|
||||||
extern void writeUltima1EnhancedMap(Archive &a);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
72
devtools/create_ultima/file.cpp
Normal file
72
devtools/create_ultima/file.cpp
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
/* ScummVM - Graphic Adventure Engine
|
||||||
|
*
|
||||||
|
* ScummVM is the legal property of its developers, whose names
|
||||||
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||||
|
* file distributed with this source distribution.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "file.h"
|
||||||
|
|
||||||
|
void Surface::setPaletteEntry(byte index, byte r, byte g, byte b) {
|
||||||
|
byte *pal = _palette + index * 3;
|
||||||
|
pal[0] = r;
|
||||||
|
pal[1] = g;
|
||||||
|
pal[2] = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Surface::saveToFile(const char *filename) {
|
||||||
|
WriteFile f(filename);
|
||||||
|
f.writeByte('B');
|
||||||
|
f.writeByte('M');
|
||||||
|
f.writeLong(0x436 + _w * _h + 2); // File size
|
||||||
|
f.writeWord(0); // Custom 1
|
||||||
|
f.writeWord(0); // Custom 2
|
||||||
|
f.writeLong(0x436); // Pixels offset
|
||||||
|
|
||||||
|
int pitch = _w;
|
||||||
|
if (pitch % 4)
|
||||||
|
pitch += 4 - (pitch % 4);
|
||||||
|
|
||||||
|
f.writeLong(40); // Info size
|
||||||
|
f.writeLong(_w); // Width
|
||||||
|
f.writeLong(_h); // Height
|
||||||
|
f.writeWord(1); // # Planes
|
||||||
|
f.writeWord(8); // Bits per pixel
|
||||||
|
f.writeLong(0); // Compression
|
||||||
|
f.writeLong(pitch * _h); // Image size
|
||||||
|
f.writeLong(3790); // Pixels per meter X
|
||||||
|
f.writeLong(3800); // Pixels per meter Y
|
||||||
|
f.writeLong(0); // color count
|
||||||
|
f.writeLong(0); // important colors
|
||||||
|
|
||||||
|
// Palette
|
||||||
|
byte *pal = _palette;
|
||||||
|
for (int idx = 0; idx < 256; ++idx, pal += 3) {
|
||||||
|
f.write(pal, 3);
|
||||||
|
f.writeByte(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write out each line from the bottom up
|
||||||
|
for (int y = _h - 1; y >= 0; --y) {
|
||||||
|
byte *lineP = getBasePtr(0, y);
|
||||||
|
f.write(lineP, _w);
|
||||||
|
|
||||||
|
if (_w % 4)
|
||||||
|
f.writeRepeating(0, 4 - (_w % 4));
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,261 +20,127 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __FILE_H__
|
#ifndef FILE_H
|
||||||
#define __FILE_H__
|
#define FILE_H
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
typedef unsigned char byte;
|
||||||
|
|
||||||
#include "common/scummsys.h"
|
class File {
|
||||||
#include "common/endian.h"
|
private:
|
||||||
#include "common/util.h"
|
FILE *_file;
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
enum AccessMode {
|
|
||||||
kFileReadMode = 1,
|
|
||||||
kFileWriteMode = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
class Stream {
|
|
||||||
public:
|
public:
|
||||||
Stream() {}
|
File(const char *filename) {
|
||||||
virtual ~Stream() {}
|
fopen_s(&_file, filename, "rb");
|
||||||
|
assert(_file);
|
||||||
virtual int seek(int offset, int whence = SEEK_SET) = 0;
|
|
||||||
virtual long read(void *buffer, size_t len) = 0;
|
|
||||||
virtual void write(const void *buffer, size_t len) = 0;
|
|
||||||
virtual uint pos() const = 0;
|
|
||||||
virtual uint size() const = 0;
|
|
||||||
virtual bool eof() const = 0;
|
|
||||||
|
|
||||||
void skip(int offset) {
|
|
||||||
seek(offset, SEEK_CUR);
|
|
||||||
}
|
}
|
||||||
void write(Stream &src, size_t len) {
|
~File() {
|
||||||
for (size_t idx = 0; idx < len; ++idx)
|
fclose(_file);
|
||||||
writeByte(src.readByte());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int seek(int offset, int origin = SEEK_SET) {
|
||||||
|
return fseek(_file, offset, origin);
|
||||||
|
}
|
||||||
|
|
||||||
byte readByte() {
|
byte readByte() {
|
||||||
byte v;
|
int b;
|
||||||
read(&v, sizeof(byte));
|
fread(&b, 1, 1, _file);
|
||||||
return v;
|
return b;
|
||||||
}
|
|
||||||
uint16 readWord() {
|
|
||||||
uint16 v;
|
|
||||||
read(&v, sizeof(uint16));
|
|
||||||
return FROM_LE_16(v);
|
|
||||||
}
|
|
||||||
uint readLong() {
|
|
||||||
uint v;
|
|
||||||
read(&v, sizeof(uint));
|
|
||||||
return FROM_LE_32(v);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint readUint16BE() {
|
int readWord() {
|
||||||
uint16 v;
|
byte b1, b2;
|
||||||
read(&v, sizeof(uint16));
|
fread(&b1, 1, 1, _file);
|
||||||
return FROM_BE_16(v);
|
fread(&b2, 1, 1, _file);
|
||||||
}
|
return b1 | (b2 << 8);
|
||||||
uint readUint16LE() {
|
|
||||||
uint16 v;
|
|
||||||
read(&v, sizeof(uint16));
|
|
||||||
return FROM_LE_16(v);
|
|
||||||
}
|
|
||||||
uint readUint32BE() {
|
|
||||||
uint32 v;
|
|
||||||
read(&v, sizeof(uint32));
|
|
||||||
return FROM_BE_32(v);
|
|
||||||
}
|
|
||||||
uint readUint32LE() {
|
|
||||||
uint32 v;
|
|
||||||
read(&v, sizeof(uint32));
|
|
||||||
return FROM_LE_32(v);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeByte(byte v) {
|
void read(void *buf, int size) {
|
||||||
write(&v, sizeof(byte));
|
fread(buf, 1, size, _file);
|
||||||
}
|
|
||||||
void writeShort(int8 v) {
|
|
||||||
write(&v, sizeof(int8));
|
|
||||||
}
|
|
||||||
void writeByte(byte v, int len) {
|
|
||||||
byte *b = new byte[len];
|
|
||||||
memset(b, v, len);
|
|
||||||
write(b, len);
|
|
||||||
delete[] b;
|
|
||||||
}
|
|
||||||
void writeWord(uint16 v) {
|
|
||||||
uint16 vTemp = TO_LE_16(v);
|
|
||||||
write(&vTemp, sizeof(uint16));
|
|
||||||
}
|
|
||||||
void writeLong(uint v) {
|
|
||||||
uint vTemp = TO_LE_32(v);
|
|
||||||
write(&vTemp, sizeof(uint));
|
|
||||||
}
|
|
||||||
void writeString(const char *msg) {
|
|
||||||
if (!msg) {
|
|
||||||
writeByte(0);
|
|
||||||
} else {
|
|
||||||
do {
|
|
||||||
writeByte(*msg);
|
|
||||||
} while (*msg++);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class File : public Stream {
|
class WriteFile {
|
||||||
private:
|
private:
|
||||||
::FILE *_f;
|
FILE *_file;
|
||||||
public:
|
public:
|
||||||
File() : _f(nullptr) {}
|
WriteFile(const char *filename) {
|
||||||
virtual ~File() { close(); }
|
fopen_s(&_file, filename, "wb");
|
||||||
|
assert(_file);
|
||||||
bool open(const char *filename, AccessMode mode = kFileReadMode) {
|
|
||||||
_f = fopen(filename, (mode == kFileReadMode) ? "rb" : "wb+");
|
|
||||||
return (_f != NULL);
|
|
||||||
}
|
}
|
||||||
void close() {
|
~WriteFile() {
|
||||||
if (_f)
|
fclose(_file);
|
||||||
fclose(_f);
|
|
||||||
_f = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual int seek(int offset, int whence = SEEK_SET) {
|
void writeByte(byte val) {
|
||||||
return fseek(_f, offset, whence);
|
fwrite(&val, 1, 1, _file);
|
||||||
}
|
}
|
||||||
virtual long read(void *buffer, size_t len) {
|
|
||||||
return fread(buffer, 1, len, _f);
|
void writeWord(int val) {
|
||||||
|
int b1 = val & 0xff, b2 = (val >> 8) & 0xff;
|
||||||
|
fwrite(&b1, 1, 1, _file);
|
||||||
|
fwrite(&b2, 1, 1, _file);
|
||||||
}
|
}
|
||||||
virtual void write(const void *buffer, size_t len) {
|
|
||||||
assert(_f);
|
void writeLong(long val) {
|
||||||
fwrite(buffer, 1, len, _f);
|
writeWord(val & 0xffff);
|
||||||
|
writeWord((val >> 16) & 0xffff);
|
||||||
}
|
}
|
||||||
virtual uint pos() const {
|
|
||||||
return ftell(_f);
|
int write(void *buf, int size) {
|
||||||
|
return (int)fwrite(buf, 1, size, _file);
|
||||||
}
|
}
|
||||||
virtual uint size() const {
|
void writeRepeating(byte val, size_t count) {
|
||||||
uint currentPos = pos();
|
while (count-- > 0)
|
||||||
fseek(_f, 0, SEEK_END);
|
writeByte(val);
|
||||||
uint result = pos();
|
|
||||||
fseek(_f, currentPos, SEEK_SET);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
virtual bool eof() const {
|
|
||||||
return feof(_f) != 0;
|
|
||||||
}
|
|
||||||
bool isOpen() const {
|
|
||||||
return _f != nullptr;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MAX_MEM_SIZE 65536
|
/**
|
||||||
|
* Simple surface structure
|
||||||
|
*/
|
||||||
|
struct Surface {
|
||||||
|
int _w;
|
||||||
|
int _h;
|
||||||
|
byte *_pixels;
|
||||||
|
byte _palette[768];
|
||||||
|
|
||||||
class MemFile : public Stream {
|
Surface(int w, int h) : _w(w), _h(h) {
|
||||||
private:
|
_pixels = new byte[w * h];
|
||||||
byte _data[MAX_MEM_SIZE];
|
memset(_pixels, 0xff, w * h);
|
||||||
size_t _size, _offset;
|
|
||||||
public:
|
|
||||||
MemFile() : _size(0), _offset(0) {
|
|
||||||
memset(_data, 0, MAX_MEM_SIZE);
|
|
||||||
}
|
|
||||||
MemFile(const byte *data, size_t size) : _size(size), _offset(0) {
|
|
||||||
memcpy(_data, data, size);
|
|
||||||
}
|
|
||||||
virtual ~MemFile() {}
|
|
||||||
|
|
||||||
bool open() {
|
// Set a default palette
|
||||||
memset(_data, 0, MAX_MEM_SIZE);
|
for (int idx = 0; idx < 256; ++idx) {
|
||||||
_size = _offset = 0;
|
memset(_palette + idx * 3, idx, 3);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
void close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual int seek(int offset, int whence = SEEK_SET) {
|
|
||||||
switch (whence) {
|
|
||||||
case SEEK_SET: _offset = whence; break;
|
|
||||||
case SEEK_CUR: _offset += whence; break;
|
|
||||||
case SEEK_END: _offset = _size + whence; break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return _offset;
|
|
||||||
}
|
|
||||||
virtual long read(void *buffer, size_t len) {
|
|
||||||
len = MAX(len, _size - _offset);
|
|
||||||
memcpy(buffer, &_data[_offset], len);
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
virtual void write(const void *buffer, size_t len) {
|
|
||||||
assert(len <= (MAX_MEM_SIZE - _offset));
|
|
||||||
memcpy(&_data[_offset], buffer, len);
|
|
||||||
_offset += len;
|
|
||||||
_size = MAX(_offset, _size);
|
|
||||||
}
|
|
||||||
virtual void write(Stream &src, size_t size) {
|
|
||||||
byte *buffer = new byte[size];
|
|
||||||
src.read(buffer, size);
|
|
||||||
write(buffer, size);
|
|
||||||
delete[] buffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual uint pos() const {
|
~Surface() {
|
||||||
return _offset;
|
delete[] _pixels;
|
||||||
}
|
|
||||||
virtual uint size() const {
|
|
||||||
return _size;
|
|
||||||
}
|
|
||||||
virtual bool eof() const {
|
|
||||||
return _offset >= _size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const byte *getData() const { return _data; }
|
Surface &operator=(const Surface &src) {
|
||||||
|
assert(src._w == _w && src._h == _h);
|
||||||
|
memcpy(_pixels, src._pixels, _w * _h);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
void syncString(const char *str) {
|
byte *getBasePtr(int x, int y) {
|
||||||
write(str, strlen(str) + 1);
|
assert(y < _h);
|
||||||
}
|
return _pixels + (y * _w) + x;
|
||||||
void syncStrings(const char *const *str, int count) {
|
|
||||||
writeLong(MKTAG(count, 0, 0, 0));
|
|
||||||
for (int idx = 0; idx < count; ++idx, ++str)
|
|
||||||
writeString(*str);
|
|
||||||
}
|
|
||||||
void syncStrings2D(const char *const *str, int count1, int count2) {
|
|
||||||
writeLong(MKTAG(count1, count2, 0, 0));
|
|
||||||
for (int idx = 0; idx < count1 * count2; ++idx, ++str)
|
|
||||||
writeString(*str);
|
|
||||||
}
|
|
||||||
void syncNumber(const int val) {
|
|
||||||
writeLong(val);
|
|
||||||
}
|
|
||||||
void syncNumbers(const int *vals, int count) {
|
|
||||||
writeLong(MKTAG(count, 0, 0, 0));
|
|
||||||
for (int idx = 0; idx < count; ++idx, ++vals)
|
|
||||||
writeLong(*vals);
|
|
||||||
}
|
|
||||||
void syncNumbers2D(const int *vals, int count1, int count2) {
|
|
||||||
writeLong(MKTAG(count1, count2, 0, 0));
|
|
||||||
for (int idx = 0; idx < count1 * count2; ++idx, ++vals)
|
|
||||||
writeLong(*vals);
|
|
||||||
}
|
|
||||||
void syncNumbers3D(const int *vals, int count1, int count2, int count3) {
|
|
||||||
writeLong(MKTAG(count1, count2, count3, 0));
|
|
||||||
for (int idx = 0; idx < count1 * count2 * count3; ++idx, ++vals)
|
|
||||||
writeLong(*vals);
|
|
||||||
}
|
|
||||||
void syncNumbers4D(const int *vals, int count1, int count2, int count3, int count4) {
|
|
||||||
writeLong(MKTAG(count1, count2, count3, count4));
|
|
||||||
for (int idx = 0; idx < count1 * count2 * count3 * count4; ++idx, ++vals)
|
|
||||||
writeLong(*vals);
|
|
||||||
}
|
|
||||||
void syncBytes2D(const byte *vals, int count1, int count2) {
|
|
||||||
writeLong(MKTAG(count1, count2, 0, 0));
|
|
||||||
write(vals, count1 * count2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setPaletteEntry(byte index, byte r, byte g, byte b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save to a BMP file
|
||||||
|
*/
|
||||||
|
void saveToFile(const char *filename);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // End of namespace Common
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
/* ScummVM - Graphic Adventure Engine
|
|
||||||
*
|
|
||||||
* ScummVM is the legal property of its developers, whose names
|
|
||||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
||||||
* file distributed with this source distribution.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef COMMON_HASH_STR_H
|
|
||||||
#define COMMON_HASH_STR_H
|
|
||||||
|
|
||||||
#include "hashmap.h"
|
|
||||||
#include "str.h"
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
uint hashit(const char *str);
|
|
||||||
uint hashit_lower(const char *str); // Generate a hash based on the lowercase version of the string
|
|
||||||
inline uint hashit(const String &str) { return hashit(str.c_str()); }
|
|
||||||
inline uint hashit_lower(const String &str) { return hashit_lower(str.c_str()); }
|
|
||||||
|
|
||||||
|
|
||||||
// FIXME: The following functors obviously are not consistently named
|
|
||||||
|
|
||||||
struct CaseSensitiveString_EqualTo {
|
|
||||||
bool operator()(const String& x, const String& y) const { return x.equals(y); }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CaseSensitiveString_Hash {
|
|
||||||
uint operator()(const String& x) const { return hashit(x.c_str()); }
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct IgnoreCase_EqualTo {
|
|
||||||
bool operator()(const String& x, const String& y) const { return x.equalsIgnoreCase(y); }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IgnoreCase_Hash {
|
|
||||||
uint operator()(const String& x) const { return hashit_lower(x.c_str()); }
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Specalization of the Hash functor for String objects.
|
|
||||||
// We do case sensitve hashing here, because that is what
|
|
||||||
// the default EqualTo is compatible with. If one wants to use
|
|
||||||
// case insensitve hashing, then only because one wants to use
|
|
||||||
// IgnoreCase_EqualTo, and then one has to specify a custom
|
|
||||||
// hash anyway.
|
|
||||||
template<>
|
|
||||||
struct Hash<String> {
|
|
||||||
uint operator()(const String& s) const {
|
|
||||||
return hashit(s.c_str());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<>
|
|
||||||
struct Hash<const char *> {
|
|
||||||
uint operator()(const char *s) const {
|
|
||||||
return hashit(s);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// String map -- by default case insensitive
|
|
||||||
typedef HashMap<String, String, IgnoreCase_Hash, IgnoreCase_EqualTo> StringMap;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // End of namespace Common
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,109 +0,0 @@
|
||||||
/* ScummVM - Graphic Adventure Engine
|
|
||||||
*
|
|
||||||
* ScummVM is the legal property of its developers, whose names
|
|
||||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
||||||
* file distributed with this source distribution.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// The hash map (associative array) implementation in this file is
|
|
||||||
// based on the PyDict implementation of CPython. The erase() method
|
|
||||||
// is based on example code in the Wikipedia article on Hash tables.
|
|
||||||
|
|
||||||
#include "common/hashmap.h"
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
// Hash function for strings, taken from CPython.
|
|
||||||
uint hashit(const char *p) {
|
|
||||||
uint hash = *p << 7;
|
|
||||||
byte c;
|
|
||||||
int size = 0;
|
|
||||||
while ((c = *p++)) {
|
|
||||||
hash = (1000003 * hash) ^ c;
|
|
||||||
size++;
|
|
||||||
}
|
|
||||||
return hash ^ size;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like hashit, but converts every char to lowercase before hashing.
|
|
||||||
uint hashit_lower(const char *p) {
|
|
||||||
uint hash = tolower(*p) << 7;
|
|
||||||
byte c;
|
|
||||||
int size = 0;
|
|
||||||
while ((c = *p++)) {
|
|
||||||
hash = (1000003 * hash) ^ tolower(c);
|
|
||||||
size++;
|
|
||||||
}
|
|
||||||
return hash ^ size;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef DEBUG_HASH_COLLISIONS
|
|
||||||
static double
|
|
||||||
g_collisions = 0,
|
|
||||||
g_dummyHits = 0,
|
|
||||||
g_lookups = 0,
|
|
||||||
g_collPerLook = 0,
|
|
||||||
g_capacity = 0,
|
|
||||||
g_size = 0;
|
|
||||||
static int g_max_capacity = 0, g_max_size = 0;
|
|
||||||
static int g_totalHashmaps = 0;
|
|
||||||
static int g_stats[4] = {0,0,0,0};
|
|
||||||
|
|
||||||
void updateHashCollisionStats(int collisions, int dummyHits, int lookups, int arrsize, int nele) {
|
|
||||||
g_collisions += collisions;
|
|
||||||
g_lookups += lookups;
|
|
||||||
g_dummyHits += dummyHits;
|
|
||||||
if (lookups)
|
|
||||||
g_collPerLook += (double)collisions / (double)lookups;
|
|
||||||
g_capacity += arrsize;
|
|
||||||
g_size += nele;
|
|
||||||
g_totalHashmaps++;
|
|
||||||
|
|
||||||
if (3*nele <= 2*8)
|
|
||||||
g_stats[0]++;
|
|
||||||
if (3*nele <= 2*16)
|
|
||||||
g_stats[1]++;
|
|
||||||
if (3*nele <= 2*32)
|
|
||||||
g_stats[2]++;
|
|
||||||
if (3*nele <= 2*64)
|
|
||||||
g_stats[3]++;
|
|
||||||
|
|
||||||
g_max_capacity = MAX(g_max_capacity, arrsize);
|
|
||||||
g_max_size = MAX(g_max_size, nele);
|
|
||||||
|
|
||||||
debug("%d hashmaps: colls %.1f; dummies hit %.1f, lookups %.1f; ratio %.3f%%; size %f (max: %d); capacity %f (max: %d)",
|
|
||||||
g_totalHashmaps,
|
|
||||||
g_collisions / g_totalHashmaps,
|
|
||||||
g_dummyHits / g_totalHashmaps,
|
|
||||||
g_lookups / g_totalHashmaps,
|
|
||||||
100 * g_collPerLook / g_totalHashmaps,
|
|
||||||
g_size / g_totalHashmaps, g_max_size,
|
|
||||||
g_capacity / g_totalHashmaps, g_max_capacity);
|
|
||||||
debug(" %d less than %d; %d less than %d; %d less than %d; %d less than %d",
|
|
||||||
g_stats[0], 2*8/3,
|
|
||||||
g_stats[1],2*16/3,
|
|
||||||
g_stats[2],2*32/3,
|
|
||||||
g_stats[3],2*64/3);
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// * Should record the maximal size of the map during its lifetime, not that at its death
|
|
||||||
// * Should do some statistics: how many maps are less than 2/3*8, 2/3*16, 2/3*32, ...
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // End of namespace Common
|
|
|
@ -1,637 +0,0 @@
|
||||||
/* ScummVM - Graphic Adventure Engine
|
|
||||||
*
|
|
||||||
* ScummVM is the legal property of its developers, whose names
|
|
||||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
||||||
* file distributed with this source distribution.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// The hash map (associative array) implementation in this file is
|
|
||||||
// based on the PyDict implementation of CPython.
|
|
||||||
|
|
||||||
#ifndef COMMON_HASHMAP_H
|
|
||||||
#define COMMON_HASHMAP_H
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @def DEBUG_HASH_COLLISIONS
|
|
||||||
* Enable the following #define if you want to check how many collisions the
|
|
||||||
* code produces (many collisions indicate either a bad hash function, or a
|
|
||||||
* hash table that is too small).
|
|
||||||
*/
|
|
||||||
//#define DEBUG_HASH_COLLISIONS
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @def USE_HASHMAP_MEMORY_POOL
|
|
||||||
* Enable the following define to let HashMaps use a memory pool for the
|
|
||||||
nodes they contain. * This increases memory usage, but also can improve
|
|
||||||
speed quite a bit.
|
|
||||||
*/
|
|
||||||
#define USE_HASHMAP_MEMORY_POOL
|
|
||||||
|
|
||||||
|
|
||||||
#include "common/func.h"
|
|
||||||
|
|
||||||
#ifdef DEBUG_HASH_COLLISIONS
|
|
||||||
#include "common/debug.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_HASHMAP_MEMORY_POOL
|
|
||||||
#include "memorypool.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
// The sgi IRIX MIPSpro Compiler has difficulties with nested templates.
|
|
||||||
// This and the other __sgi conditionals below work around these problems.
|
|
||||||
// The Intel C++ Compiler suffers from the same problems.
|
|
||||||
#if (defined(__sgi) && !defined(__GNUC__)) || defined(__INTEL_COMPILER)
|
|
||||||
template<class T> class IteratorImpl;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HashMap<Key,Val> maps objects of type Key to objects of type Val.
|
|
||||||
* For each used Key type, we need an "size_type hashit(Key,size_type)" function
|
|
||||||
* that computes a hash for the given Key object and returns it as an
|
|
||||||
* an integer from 0 to hashsize-1, and also an "equality functor".
|
|
||||||
* that returns true if if its two arguments are to be considered
|
|
||||||
* equal. Also, we assume that "=" works on Val objects for assignment.
|
|
||||||
*
|
|
||||||
* If aa is an HashMap<Key,Val>, then space is allocated each time aa[key] is
|
|
||||||
* referenced, for a new key. If the object is const, then an assertion is
|
|
||||||
* triggered instead. Hence if you are not sure whether a key is contained in
|
|
||||||
* the map, use contains() first to check for its presence.
|
|
||||||
*/
|
|
||||||
template<class Key, class Val, class HashFunc = Hash<Key>, class EqualFunc = EqualTo<Key> >
|
|
||||||
class HashMap {
|
|
||||||
public:
|
|
||||||
typedef uint size_type;
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
typedef HashMap<Key, Val, HashFunc, EqualFunc> HM_t;
|
|
||||||
|
|
||||||
struct Node {
|
|
||||||
const Key _key;
|
|
||||||
Val _value;
|
|
||||||
explicit Node(const Key &key) : _key(key), _value() {}
|
|
||||||
Node() : _key(), _value() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
enum {
|
|
||||||
HASHMAP_PERTURB_SHIFT = 5,
|
|
||||||
HASHMAP_MIN_CAPACITY = 16,
|
|
||||||
|
|
||||||
// The quotient of the next two constants controls how much the
|
|
||||||
// internal storage of the hashmap may fill up before being
|
|
||||||
// increased automatically.
|
|
||||||
// Note: the quotient of these two must be between and different
|
|
||||||
// from 0 and 1.
|
|
||||||
HASHMAP_LOADFACTOR_NUMERATOR = 2,
|
|
||||||
HASHMAP_LOADFACTOR_DENOMINATOR = 3,
|
|
||||||
|
|
||||||
HASHMAP_MEMORYPOOL_SIZE = HASHMAP_MIN_CAPACITY * HASHMAP_LOADFACTOR_NUMERATOR / HASHMAP_LOADFACTOR_DENOMINATOR
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef USE_HASHMAP_MEMORY_POOL
|
|
||||||
ObjectPool<Node, HASHMAP_MEMORYPOOL_SIZE> _nodePool;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Node **_storage; ///< hashtable of size arrsize.
|
|
||||||
size_type _mask; ///< Capacity of the HashMap minus one; must be a power of two of minus one
|
|
||||||
size_type _size;
|
|
||||||
size_type _deleted; ///< Number of deleted elements (_dummyNodes)
|
|
||||||
|
|
||||||
HashFunc _hash;
|
|
||||||
EqualFunc _equal;
|
|
||||||
|
|
||||||
/** Default value, returned by the const getVal. */
|
|
||||||
const Val _defaultVal;
|
|
||||||
|
|
||||||
/** Dummy node, used as marker for erased objects. */
|
|
||||||
#define HASHMAP_DUMMY_NODE ((Node *)1)
|
|
||||||
|
|
||||||
#ifdef DEBUG_HASH_COLLISIONS
|
|
||||||
mutable int _collisions, _lookups, _dummyHits;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Node *allocNode(const Key &key) {
|
|
||||||
#ifdef USE_HASHMAP_MEMORY_POOL
|
|
||||||
return new (_nodePool) Node(key);
|
|
||||||
#else
|
|
||||||
return new Node(key);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void freeNode(Node *node) {
|
|
||||||
if (node && node != HASHMAP_DUMMY_NODE)
|
|
||||||
#ifdef USE_HASHMAP_MEMORY_POOL
|
|
||||||
_nodePool.deleteChunk(node);
|
|
||||||
#else
|
|
||||||
delete node;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void assign(const HM_t &map);
|
|
||||||
size_type lookup(const Key &key) const;
|
|
||||||
size_type lookupAndCreateIfMissing(const Key &key);
|
|
||||||
void expandStorage(size_type newCapacity);
|
|
||||||
|
|
||||||
#if !defined(__sgi) || defined(__GNUC__)
|
|
||||||
template<class T> friend class IteratorImpl;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple HashMap iterator implementation.
|
|
||||||
*/
|
|
||||||
template<class NodeType>
|
|
||||||
class IteratorImpl {
|
|
||||||
friend class HashMap;
|
|
||||||
#if (defined(__sgi) && !defined(__GNUC__)) || defined(__INTEL_COMPILER)
|
|
||||||
template<class T> friend class Common::IteratorImpl;
|
|
||||||
#else
|
|
||||||
template<class T> friend class IteratorImpl;
|
|
||||||
#endif
|
|
||||||
protected:
|
|
||||||
typedef const HashMap hashmap_t;
|
|
||||||
|
|
||||||
size_type _idx;
|
|
||||||
hashmap_t *_hashmap;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
IteratorImpl(size_type idx, hashmap_t *hashmap) : _idx(idx), _hashmap(hashmap) {}
|
|
||||||
|
|
||||||
NodeType *deref() const {
|
|
||||||
assert(_hashmap != 0);
|
|
||||||
assert(_idx <= _hashmap->_mask);
|
|
||||||
Node *node = _hashmap->_storage[_idx];
|
|
||||||
assert(node != 0);
|
|
||||||
assert(node != HASHMAP_DUMMY_NODE);
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
IteratorImpl() : _idx(0), _hashmap(0) {}
|
|
||||||
template<class T>
|
|
||||||
IteratorImpl(const IteratorImpl<T> &c) : _idx(c._idx), _hashmap(c._hashmap) {}
|
|
||||||
|
|
||||||
NodeType &operator*() const { return *deref(); }
|
|
||||||
NodeType *operator->() const { return deref(); }
|
|
||||||
|
|
||||||
bool operator==(const IteratorImpl &iter) const { return _idx == iter._idx && _hashmap == iter._hashmap; }
|
|
||||||
bool operator!=(const IteratorImpl &iter) const { return !(*this == iter); }
|
|
||||||
|
|
||||||
IteratorImpl &operator++() {
|
|
||||||
assert(_hashmap);
|
|
||||||
do {
|
|
||||||
_idx++;
|
|
||||||
} while (_idx <= _hashmap->_mask && (_hashmap->_storage[_idx] == 0 || _hashmap->_storage[_idx] == HASHMAP_DUMMY_NODE));
|
|
||||||
if (_idx > _hashmap->_mask)
|
|
||||||
_idx = (size_type)-1;
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
IteratorImpl operator++(int) {
|
|
||||||
IteratorImpl old = *this;
|
|
||||||
operator ++();
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
typedef IteratorImpl<Node> iterator;
|
|
||||||
typedef IteratorImpl<const Node> const_iterator;
|
|
||||||
|
|
||||||
HashMap();
|
|
||||||
HashMap(const HM_t &map);
|
|
||||||
~HashMap();
|
|
||||||
|
|
||||||
HM_t &operator=(const HM_t &map) {
|
|
||||||
if (this == &map)
|
|
||||||
return *this;
|
|
||||||
|
|
||||||
// Remove the previous content and ...
|
|
||||||
clear();
|
|
||||||
delete[] _storage;
|
|
||||||
// ... copy the new stuff.
|
|
||||||
assign(map);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool contains(const Key &key) const;
|
|
||||||
|
|
||||||
Val &operator[](const Key &key);
|
|
||||||
const Val &operator[](const Key &key) const;
|
|
||||||
|
|
||||||
Val &getVal(const Key &key);
|
|
||||||
const Val &getVal(const Key &key) const;
|
|
||||||
const Val &getVal(const Key &key, const Val &defaultVal) const;
|
|
||||||
void setVal(const Key &key, const Val &val);
|
|
||||||
|
|
||||||
void clear(bool shrinkArray = 0);
|
|
||||||
|
|
||||||
void erase(iterator entry);
|
|
||||||
void erase(const Key &key);
|
|
||||||
|
|
||||||
size_type size() const { return _size; }
|
|
||||||
|
|
||||||
iterator begin() {
|
|
||||||
// Find and return the first non-empty entry
|
|
||||||
for (size_type ctr = 0; ctr <= _mask; ++ctr) {
|
|
||||||
if (_storage[ctr] && _storage[ctr] != HASHMAP_DUMMY_NODE)
|
|
||||||
return iterator(ctr, this);
|
|
||||||
}
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
iterator end() {
|
|
||||||
return iterator((size_type)-1, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator begin() const {
|
|
||||||
// Find and return the first non-empty entry
|
|
||||||
for (size_type ctr = 0; ctr <= _mask; ++ctr) {
|
|
||||||
if (_storage[ctr] && _storage[ctr] != HASHMAP_DUMMY_NODE)
|
|
||||||
return const_iterator(ctr, this);
|
|
||||||
}
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
const_iterator end() const {
|
|
||||||
return const_iterator((size_type)-1, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
iterator find(const Key &key) {
|
|
||||||
size_type ctr = lookup(key);
|
|
||||||
if (_storage[ctr])
|
|
||||||
return iterator(ctr, this);
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator find(const Key &key) const {
|
|
||||||
size_type ctr = lookup(key);
|
|
||||||
if (_storage[ctr])
|
|
||||||
return const_iterator(ctr, this);
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: insert() method?
|
|
||||||
|
|
||||||
bool empty() const {
|
|
||||||
return (_size == 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//-------------------------------------------------------
|
|
||||||
// HashMap functions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base constructor, creates an empty hashmap.
|
|
||||||
*/
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
HashMap<Key, Val, HashFunc, EqualFunc>::HashMap()
|
|
||||||
//
|
|
||||||
// We have to skip _defaultVal() on PS2 to avoid gcc 3.2.2 ICE
|
|
||||||
//
|
|
||||||
#ifdef __PLAYSTATION2__
|
|
||||||
{
|
|
||||||
#else
|
|
||||||
: _defaultVal() {
|
|
||||||
#endif
|
|
||||||
_mask = HASHMAP_MIN_CAPACITY - 1;
|
|
||||||
_storage = new Node *[HASHMAP_MIN_CAPACITY];
|
|
||||||
assert(_storage != NULL);
|
|
||||||
memset(_storage, 0, HASHMAP_MIN_CAPACITY * sizeof(Node *));
|
|
||||||
|
|
||||||
_size = 0;
|
|
||||||
_deleted = 0;
|
|
||||||
|
|
||||||
#ifdef DEBUG_HASH_COLLISIONS
|
|
||||||
_collisions = 0;
|
|
||||||
_lookups = 0;
|
|
||||||
_dummyHits = 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy constructor, creates a full copy of the given hashmap.
|
|
||||||
* We must provide a custom copy constructor as we use pointers
|
|
||||||
* to heap buffers for the internal storage.
|
|
||||||
*/
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
HashMap<Key, Val, HashFunc, EqualFunc>::HashMap(const HM_t &map) :
|
|
||||||
_defaultVal() {
|
|
||||||
#ifdef DEBUG_HASH_COLLISIONS
|
|
||||||
_collisions = 0;
|
|
||||||
_lookups = 0;
|
|
||||||
_dummyHits = 0;
|
|
||||||
#endif
|
|
||||||
assign(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destructor, frees all used memory.
|
|
||||||
*/
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
HashMap<Key, Val, HashFunc, EqualFunc>::~HashMap() {
|
|
||||||
for (size_type ctr = 0; ctr <= _mask; ++ctr)
|
|
||||||
freeNode(_storage[ctr]);
|
|
||||||
|
|
||||||
delete[] _storage;
|
|
||||||
#ifdef DEBUG_HASH_COLLISIONS
|
|
||||||
extern void updateHashCollisionStats(int, int, int, int, int);
|
|
||||||
updateHashCollisionStats(_collisions, _dummyHits, _lookups, _mask+1, _size);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal method for assigning the content of another HashMap
|
|
||||||
* to this one.
|
|
||||||
*
|
|
||||||
* @note We do *not* deallocate the previous storage here -- the caller is
|
|
||||||
* responsible for doing that!
|
|
||||||
*/
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
void HashMap<Key, Val, HashFunc, EqualFunc>::assign(const HM_t &map) {
|
|
||||||
_mask = map._mask;
|
|
||||||
_storage = new Node *[_mask+1];
|
|
||||||
assert(_storage != NULL);
|
|
||||||
memset(_storage, 0, (_mask+1) * sizeof(Node *));
|
|
||||||
|
|
||||||
// Simply clone the map given to us, one by one.
|
|
||||||
_size = 0;
|
|
||||||
_deleted = 0;
|
|
||||||
for (size_type ctr = 0; ctr <= _mask; ++ctr) {
|
|
||||||
if (map._storage[ctr] == HASHMAP_DUMMY_NODE) {
|
|
||||||
_storage[ctr] = HASHMAP_DUMMY_NODE;
|
|
||||||
_deleted++;
|
|
||||||
} else if (map._storage[ctr] != NULL) {
|
|
||||||
_storage[ctr] = allocNode(map._storage[ctr]->_key);
|
|
||||||
_storage[ctr]->_value = map._storage[ctr]->_value;
|
|
||||||
_size++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Perform a sanity check (to help track down hashmap corruption)
|
|
||||||
assert(_size == map._size);
|
|
||||||
assert(_deleted == map._deleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
void HashMap<Key, Val, HashFunc, EqualFunc>::clear(bool shrinkArray) {
|
|
||||||
for (size_type ctr = 0; ctr <= _mask; ++ctr) {
|
|
||||||
freeNode(_storage[ctr]);
|
|
||||||
_storage[ctr] = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_HASHMAP_MEMORY_POOL
|
|
||||||
_nodePool.freeUnusedPages();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (shrinkArray && _mask >= HASHMAP_MIN_CAPACITY) {
|
|
||||||
delete[] _storage;
|
|
||||||
|
|
||||||
_mask = HASHMAP_MIN_CAPACITY;
|
|
||||||
_storage = new Node *[HASHMAP_MIN_CAPACITY];
|
|
||||||
assert(_storage != NULL);
|
|
||||||
memset(_storage, 0, HASHMAP_MIN_CAPACITY * sizeof(Node *));
|
|
||||||
}
|
|
||||||
|
|
||||||
_size = 0;
|
|
||||||
_deleted = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
void HashMap<Key, Val, HashFunc, EqualFunc>::expandStorage(size_type newCapacity) {
|
|
||||||
assert(newCapacity > _mask+1);
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
const size_type old_size = _size;
|
|
||||||
#endif
|
|
||||||
const size_type old_mask = _mask;
|
|
||||||
Node **old_storage = _storage;
|
|
||||||
|
|
||||||
// allocate a new array
|
|
||||||
_size = 0;
|
|
||||||
_deleted = 0;
|
|
||||||
_mask = newCapacity - 1;
|
|
||||||
_storage = new Node *[newCapacity];
|
|
||||||
assert(_storage != NULL);
|
|
||||||
memset(_storage, 0, newCapacity * sizeof(Node *));
|
|
||||||
|
|
||||||
// rehash all the old elements
|
|
||||||
for (size_type ctr = 0; ctr <= old_mask; ++ctr) {
|
|
||||||
if (old_storage[ctr] == NULL || old_storage[ctr] == HASHMAP_DUMMY_NODE)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Insert the element from the old table into the new table.
|
|
||||||
// Since we know that no key exists twice in the old table, we
|
|
||||||
// can do this slightly better than by calling lookup, since we
|
|
||||||
// don't have to call _equal().
|
|
||||||
const size_type hash = _hash(old_storage[ctr]->_key);
|
|
||||||
size_type idx = hash & _mask;
|
|
||||||
for (size_type perturb = hash; _storage[idx] != NULL && _storage[idx] != HASHMAP_DUMMY_NODE; perturb >>= HASHMAP_PERTURB_SHIFT) {
|
|
||||||
idx = (5 * idx + perturb + 1) & _mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
_storage[idx] = old_storage[ctr];
|
|
||||||
_size++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform a sanity check: Old number of elements should match the new one!
|
|
||||||
// This check will fail if some previous operation corrupted this hashmap.
|
|
||||||
assert(_size == old_size);
|
|
||||||
|
|
||||||
delete[] old_storage;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
typename HashMap<Key, Val, HashFunc, EqualFunc>::size_type HashMap<Key, Val, HashFunc, EqualFunc>::lookup(const Key &key) const {
|
|
||||||
const size_type hash = _hash(key);
|
|
||||||
size_type ctr = hash & _mask;
|
|
||||||
for (size_type perturb = hash; ; perturb >>= HASHMAP_PERTURB_SHIFT) {
|
|
||||||
if (_storage[ctr] == NULL)
|
|
||||||
break;
|
|
||||||
if (_storage[ctr] == HASHMAP_DUMMY_NODE) {
|
|
||||||
#ifdef DEBUG_HASH_COLLISIONS
|
|
||||||
_dummyHits++;
|
|
||||||
#endif
|
|
||||||
} else if (_equal(_storage[ctr]->_key, key))
|
|
||||||
break;
|
|
||||||
|
|
||||||
ctr = (5 * ctr + perturb + 1) & _mask;
|
|
||||||
|
|
||||||
#ifdef DEBUG_HASH_COLLISIONS
|
|
||||||
_collisions++;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef DEBUG_HASH_COLLISIONS
|
|
||||||
_lookups++;
|
|
||||||
debug("collisions %d, dummies hit %d, lookups %d, ratio %f in HashMap %p; size %d num elements %d",
|
|
||||||
_collisions, _dummyHits, _lookups, ((double) _collisions / (double)_lookups),
|
|
||||||
(const void *)this, _mask+1, _size);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return ctr;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
typename HashMap<Key, Val, HashFunc, EqualFunc>::size_type HashMap<Key, Val, HashFunc, EqualFunc>::lookupAndCreateIfMissing(const Key &key) {
|
|
||||||
const size_type hash = _hash(key);
|
|
||||||
size_type ctr = hash & _mask;
|
|
||||||
const size_type NONE_FOUND = _mask + 1;
|
|
||||||
size_type first_free = NONE_FOUND;
|
|
||||||
bool found = false;
|
|
||||||
for (size_type perturb = hash; ; perturb >>= HASHMAP_PERTURB_SHIFT) {
|
|
||||||
if (_storage[ctr] == NULL)
|
|
||||||
break;
|
|
||||||
if (_storage[ctr] == HASHMAP_DUMMY_NODE) {
|
|
||||||
#ifdef DEBUG_HASH_COLLISIONS
|
|
||||||
_dummyHits++;
|
|
||||||
#endif
|
|
||||||
if (first_free != _mask + 1)
|
|
||||||
first_free = ctr;
|
|
||||||
} else if (_equal(_storage[ctr]->_key, key)) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctr = (5 * ctr + perturb + 1) & _mask;
|
|
||||||
|
|
||||||
#ifdef DEBUG_HASH_COLLISIONS
|
|
||||||
_collisions++;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef DEBUG_HASH_COLLISIONS
|
|
||||||
_lookups++;
|
|
||||||
debug("collisions %d, dummies hit %d, lookups %d, ratio %f in HashMap %p; size %d num elements %d",
|
|
||||||
_collisions, _dummyHits, _lookups, ((double) _collisions / (double)_lookups),
|
|
||||||
(const void *)this, _mask+1, _size);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!found && first_free != _mask + 1)
|
|
||||||
ctr = first_free;
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
if (_storage[ctr])
|
|
||||||
_deleted--;
|
|
||||||
_storage[ctr] = allocNode(key);
|
|
||||||
assert(_storage[ctr] != NULL);
|
|
||||||
_size++;
|
|
||||||
|
|
||||||
// Keep the load factor below a certain threshold.
|
|
||||||
// Deleted nodes are also counted
|
|
||||||
size_type capacity = _mask + 1;
|
|
||||||
if ((_size + _deleted) * HASHMAP_LOADFACTOR_DENOMINATOR >
|
|
||||||
capacity * HASHMAP_LOADFACTOR_NUMERATOR) {
|
|
||||||
capacity = capacity < 500 ? (capacity * 4) : (capacity * 2);
|
|
||||||
expandStorage(capacity);
|
|
||||||
ctr = lookup(key);
|
|
||||||
assert(_storage[ctr] != NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
bool HashMap<Key, Val, HashFunc, EqualFunc>::contains(const Key &key) const {
|
|
||||||
size_type ctr = lookup(key);
|
|
||||||
return (_storage[ctr] != NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
Val &HashMap<Key, Val, HashFunc, EqualFunc>::operator[](const Key &key) {
|
|
||||||
return getVal(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
const Val &HashMap<Key, Val, HashFunc, EqualFunc>::operator[](const Key &key) const {
|
|
||||||
return getVal(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
Val &HashMap<Key, Val, HashFunc, EqualFunc>::getVal(const Key &key) {
|
|
||||||
size_type ctr = lookupAndCreateIfMissing(key);
|
|
||||||
assert(_storage[ctr] != NULL);
|
|
||||||
return _storage[ctr]->_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
const Val &HashMap<Key, Val, HashFunc, EqualFunc>::getVal(const Key &key) const {
|
|
||||||
return getVal(key, _defaultVal);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
const Val &HashMap<Key, Val, HashFunc, EqualFunc>::getVal(const Key &key, const Val &defaultVal) const {
|
|
||||||
size_type ctr = lookup(key);
|
|
||||||
if (_storage[ctr] != NULL)
|
|
||||||
return _storage[ctr]->_value;
|
|
||||||
else
|
|
||||||
return defaultVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
void HashMap<Key, Val, HashFunc, EqualFunc>::setVal(const Key &key, const Val &val) {
|
|
||||||
size_type ctr = lookupAndCreateIfMissing(key);
|
|
||||||
assert(_storage[ctr] != NULL);
|
|
||||||
_storage[ctr]->_value = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
void HashMap<Key, Val, HashFunc, EqualFunc>::erase(iterator entry) {
|
|
||||||
// Check whether we have a valid iterator
|
|
||||||
assert(entry._hashmap == this);
|
|
||||||
const size_type ctr = entry._idx;
|
|
||||||
assert(ctr <= _mask);
|
|
||||||
Node * const node = _storage[ctr];
|
|
||||||
assert(node != NULL);
|
|
||||||
assert(node != HASHMAP_DUMMY_NODE);
|
|
||||||
|
|
||||||
// If we remove a key, we replace it with a dummy node.
|
|
||||||
freeNode(node);
|
|
||||||
_storage[ctr] = HASHMAP_DUMMY_NODE;
|
|
||||||
_size--;
|
|
||||||
_deleted++;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Key, class Val, class HashFunc, class EqualFunc>
|
|
||||||
void HashMap<Key, Val, HashFunc, EqualFunc>::erase(const Key &key) {
|
|
||||||
|
|
||||||
size_type ctr = lookup(key);
|
|
||||||
if (_storage[ctr] == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If we remove a key, we replace it with a dummy node.
|
|
||||||
freeNode(_storage[ctr]);
|
|
||||||
_storage[ctr] = HASHMAP_DUMMY_NODE;
|
|
||||||
_size--;
|
|
||||||
_deleted++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef HASHMAP_DUMMY_NODE
|
|
||||||
|
|
||||||
} // End of namespace Common
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,182 +0,0 @@
|
||||||
/* ScummVM - Graphic Adventure Engine
|
|
||||||
*
|
|
||||||
* ScummVM is the legal property of its developers, whose names
|
|
||||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
||||||
* file distributed with this source distribution.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "memorypool.h"
|
|
||||||
#include "common/util.h"
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
enum {
|
|
||||||
INITIAL_CHUNKS_PER_PAGE = 8
|
|
||||||
};
|
|
||||||
|
|
||||||
static size_t adjustChunkSize(size_t chunkSize) {
|
|
||||||
// You must at least fit the pointer in the node (technically unneeded considering the next rounding statement)
|
|
||||||
chunkSize = MAX(chunkSize, sizeof(void *));
|
|
||||||
// There might be an alignment problem on some platforms when trying to load a void* on a non natural boundary
|
|
||||||
// so we round to the next sizeof(void *)
|
|
||||||
chunkSize = (chunkSize + sizeof(void *) - 1) & (~(sizeof(void *) - 1));
|
|
||||||
|
|
||||||
return chunkSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
MemoryPool::MemoryPool(size_t chunkSize)
|
|
||||||
: _chunkSize(adjustChunkSize(chunkSize)) {
|
|
||||||
|
|
||||||
_next = NULL;
|
|
||||||
|
|
||||||
_chunksPerPage = INITIAL_CHUNKS_PER_PAGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
MemoryPool::~MemoryPool() {
|
|
||||||
#if 0
|
|
||||||
freeUnusedPages();
|
|
||||||
if (!_pages.empty())
|
|
||||||
warning("Memory leak found in pool");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for (size_t i = 0; i < _pages.size(); ++i)
|
|
||||||
::free(_pages[i].start);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryPool::allocPage() {
|
|
||||||
Page page;
|
|
||||||
|
|
||||||
// Allocate a new page
|
|
||||||
page.numChunks = _chunksPerPage;
|
|
||||||
assert(page.numChunks * _chunkSize < 16*1024*1024); // Refuse to allocate pages bigger than 16 MB
|
|
||||||
|
|
||||||
page.start = ::malloc(page.numChunks * _chunkSize);
|
|
||||||
assert(page.start);
|
|
||||||
_pages.push_back(page);
|
|
||||||
|
|
||||||
|
|
||||||
// Next time, we'll allocate a page twice as big as this one.
|
|
||||||
_chunksPerPage *= 2;
|
|
||||||
|
|
||||||
// Add the page to the pool of free chunk
|
|
||||||
addPageToPool(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryPool::addPageToPool(const Page &page) {
|
|
||||||
// Add all chunks of the new page to the linked list (pool) of free chunks
|
|
||||||
void *current = page.start;
|
|
||||||
for (size_t i = 1; i < page.numChunks; ++i) {
|
|
||||||
void *next = (byte *)current + _chunkSize;
|
|
||||||
*(void **)current = next;
|
|
||||||
|
|
||||||
current = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Last chunk points to the old _next
|
|
||||||
*(void **)current = _next;
|
|
||||||
|
|
||||||
// From now on, the first free chunk is the first chunk of the new page
|
|
||||||
_next = page.start;
|
|
||||||
}
|
|
||||||
|
|
||||||
void *MemoryPool::allocChunk() {
|
|
||||||
// No free chunks left? Allocate a new page
|
|
||||||
if (!_next)
|
|
||||||
allocPage();
|
|
||||||
|
|
||||||
assert(_next);
|
|
||||||
void *result = _next;
|
|
||||||
_next = *(void **)result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryPool::freeChunk(void *ptr) {
|
|
||||||
// Add the chunk back to (the start of) the list of free chunks
|
|
||||||
*(void **)ptr = _next;
|
|
||||||
_next = ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Technically not compliant C++ to compare unrelated pointers. In practice...
|
|
||||||
bool MemoryPool::isPointerInPage(void *ptr, const Page &page) {
|
|
||||||
return (ptr >= page.start) && (ptr < (char *)page.start + page.numChunks * _chunkSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryPool::freeUnusedPages() {
|
|
||||||
//std::sort(_pages.begin(), _pages.end());
|
|
||||||
Array<size_t> numberOfFreeChunksPerPage;
|
|
||||||
numberOfFreeChunksPerPage.resize(_pages.size());
|
|
||||||
for (size_t i = 0; i < numberOfFreeChunksPerPage.size(); ++i) {
|
|
||||||
numberOfFreeChunksPerPage[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute for each page how many chunks in it are still in use.
|
|
||||||
void *iterator = _next;
|
|
||||||
while (iterator) {
|
|
||||||
// TODO: This should be a binary search (requiring us to keep _pages sorted)
|
|
||||||
for (size_t i = 0; i < _pages.size(); ++i) {
|
|
||||||
if (isPointerInPage(iterator, _pages[i])) {
|
|
||||||
++numberOfFreeChunksPerPage[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iterator = *(void **)iterator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Free all pages which are not in use.
|
|
||||||
size_t freedPagesCount = 0;
|
|
||||||
for (size_t i = 0; i < _pages.size(); ++i) {
|
|
||||||
if (numberOfFreeChunksPerPage[i] == _pages[i].numChunks) {
|
|
||||||
// Remove all chunks of this page from the list of free chunks
|
|
||||||
void **iter2 = &_next;
|
|
||||||
while (*iter2) {
|
|
||||||
if (isPointerInPage(*iter2, _pages[i]))
|
|
||||||
*iter2 = **(void ***)iter2;
|
|
||||||
else
|
|
||||||
iter2 = *(void ***)iter2;
|
|
||||||
}
|
|
||||||
|
|
||||||
::free(_pages[i].start);
|
|
||||||
++freedPagesCount;
|
|
||||||
_pages[i].start = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// debug("freed %d pages out of %d", (int)freedPagesCount, (int)_pages.size());
|
|
||||||
|
|
||||||
// Remove all now unused pages
|
|
||||||
size_t newSize = 0;
|
|
||||||
for (size_t i = 0; i < _pages.size(); ++i) {
|
|
||||||
if (_pages[i].start != NULL) {
|
|
||||||
if (newSize != i)
|
|
||||||
_pages[newSize] = _pages[i];
|
|
||||||
++newSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_pages.resize(newSize);
|
|
||||||
|
|
||||||
// Reset _chunksPerPage
|
|
||||||
_chunksPerPage = INITIAL_CHUNKS_PER_PAGE;
|
|
||||||
for (size_t i = 0; i < _pages.size(); ++i) {
|
|
||||||
if (_chunksPerPage < _pages[i].numChunks)
|
|
||||||
_chunksPerPage = _pages[i].numChunks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // End of namespace Common
|
|
|
@ -1,162 +0,0 @@
|
||||||
/* ScummVM - Graphic Adventure Engine
|
|
||||||
*
|
|
||||||
* ScummVM is the legal property of its developers, whose names
|
|
||||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
||||||
* file distributed with this source distribution.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef COMMON_MEMORYPOOL_H
|
|
||||||
#define COMMON_MEMORYPOOL_H
|
|
||||||
|
|
||||||
#include "common/array.h"
|
|
||||||
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class provides a pool of memory 'chunks' of identical size.
|
|
||||||
* The size of a chunk is determined when creating the memory pool.
|
|
||||||
*
|
|
||||||
* Using a memory pool may yield better performance and memory usage
|
|
||||||
* when allocating and deallocating many memory blocks of equal size.
|
|
||||||
* E.g. the Common::String class uses a memory pool for the refCount
|
|
||||||
* variables (each the size of an int) it allocates for each string
|
|
||||||
* instance.
|
|
||||||
*/
|
|
||||||
class MemoryPool {
|
|
||||||
protected:
|
|
||||||
MemoryPool(const MemoryPool&);
|
|
||||||
MemoryPool& operator=(const MemoryPool&);
|
|
||||||
|
|
||||||
struct Page {
|
|
||||||
void *start;
|
|
||||||
size_t numChunks;
|
|
||||||
};
|
|
||||||
|
|
||||||
const size_t _chunkSize;
|
|
||||||
Array<Page> _pages;
|
|
||||||
void *_next;
|
|
||||||
size_t _chunksPerPage;
|
|
||||||
|
|
||||||
void allocPage();
|
|
||||||
void addPageToPool(const Page &page);
|
|
||||||
bool isPointerInPage(void *ptr, const Page &page);
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Constructor for a memory pool with the given chunk size.
|
|
||||||
* @param chunkSize the chunk size of this memory pool
|
|
||||||
*/
|
|
||||||
explicit MemoryPool(size_t chunkSize);
|
|
||||||
~MemoryPool();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocate a new chunk from the memory pool.
|
|
||||||
*/
|
|
||||||
void *allocChunk();
|
|
||||||
/**
|
|
||||||
* Return a chunk to the memory pool. The given pointer must have
|
|
||||||
* been obtained from calling the allocChunk() method of the very
|
|
||||||
* same MemoryPool instance. Passing any other pointer (e.g. to
|
|
||||||
* a chunk from another MemoryPool, or a malloc'ed memory block)
|
|
||||||
* will lead to undefined behavior and may result in a crash (if
|
|
||||||
* you are lucky) or in silent data corruption.
|
|
||||||
*/
|
|
||||||
void freeChunk(void *ptr);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform garbage collection. The memory pool stores all the
|
|
||||||
* chunks it manages in memory 'pages' obtained via the classic
|
|
||||||
* memory allocation APIs (i.e. malloc/free). Ordinarily, once
|
|
||||||
* a page has been allocated, it won't be released again during
|
|
||||||
* the life time of the memory pool. The exception is when this
|
|
||||||
* method is called.
|
|
||||||
*/
|
|
||||||
void freeUnusedPages();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the chunk size used by this memory pool.
|
|
||||||
*/
|
|
||||||
size_t getChunkSize() const { return _chunkSize; }
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a memory pool which already contains in itself some storage
|
|
||||||
* space for a fixed number of chunks. Thus if the memory pool is only
|
|
||||||
* lightly used, no malloc() calls have to be made at all.
|
|
||||||
*/
|
|
||||||
template<size_t CHUNK_SIZE, size_t NUM_INTERNAL_CHUNKS = 32>
|
|
||||||
class FixedSizeMemoryPool : public MemoryPool {
|
|
||||||
private:
|
|
||||||
enum {
|
|
||||||
REAL_CHUNK_SIZE = (CHUNK_SIZE + sizeof(void *) - 1) & (~(sizeof(void *) - 1))
|
|
||||||
};
|
|
||||||
|
|
||||||
byte _storage[NUM_INTERNAL_CHUNKS * REAL_CHUNK_SIZE];
|
|
||||||
public:
|
|
||||||
FixedSizeMemoryPool() : MemoryPool(CHUNK_SIZE) {
|
|
||||||
assert(REAL_CHUNK_SIZE == _chunkSize);
|
|
||||||
// Insert some static storage
|
|
||||||
Page internalPage = { _storage, NUM_INTERNAL_CHUNKS };
|
|
||||||
addPageToPool(internalPage);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ensure NUM_INTERNAL_CHUNKS == 0 results in a compile error
|
|
||||||
template<size_t CHUNK_SIZE>
|
|
||||||
class FixedSizeMemoryPool<CHUNK_SIZE,0> : public MemoryPool {
|
|
||||||
public:
|
|
||||||
FixedSizeMemoryPool() : MemoryPool(CHUNK_SIZE) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A memory pool for C++ objects.
|
|
||||||
*/
|
|
||||||
template<class T, size_t NUM_INTERNAL_CHUNKS = 32>
|
|
||||||
class ObjectPool : public FixedSizeMemoryPool<sizeof(T), NUM_INTERNAL_CHUNKS> {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Return the memory chunk used as storage for the given object back
|
|
||||||
* to the pool, after calling its destructor.
|
|
||||||
*/
|
|
||||||
void deleteChunk(T *ptr) {
|
|
||||||
ptr->~T();
|
|
||||||
this->freeChunk(ptr);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // End of namespace Common
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A custom placement new operator, using an arbitrary MemoryPool.
|
|
||||||
*
|
|
||||||
* This *should* work with all C++ implementations, but may not.
|
|
||||||
*
|
|
||||||
* For details on using placement new for custom allocators, see e.g.
|
|
||||||
* <http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.14>
|
|
||||||
*/
|
|
||||||
inline void *operator new(size_t nbytes, Common::MemoryPool &pool) {
|
|
||||||
assert(nbytes <= pool.getChunkSize());
|
|
||||||
return pool.allocChunk();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void operator delete(void *p, Common::MemoryPool &pool) {
|
|
||||||
pool.freeChunk(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -2,12 +2,8 @@ MODULE := devtools/create_ultima
|
||||||
|
|
||||||
MODULE_OBJS := \
|
MODULE_OBJS := \
|
||||||
create_ultima.o \
|
create_ultima.o \
|
||||||
archive.o \
|
file.o \
|
||||||
ultima1_map.o \
|
ultima1_resources.o
|
||||||
ultima1_resources.o \
|
|
||||||
hashmap.o \
|
|
||||||
memorypool.o \
|
|
||||||
str.o
|
|
||||||
|
|
||||||
# Set the name of the executable
|
# Set the name of the executable
|
||||||
TOOL_EXECUTABLE := create_ultima
|
TOOL_EXECUTABLE := create_ultima
|
||||||
|
|
|
@ -1,786 +0,0 @@
|
||||||
/* ScummVM - Graphic Adventure Engine
|
|
||||||
*
|
|
||||||
* ScummVM is the legal property of its developers, whose names
|
|
||||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
||||||
* file distributed with this source distribution.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "common/hash-str.h"
|
|
||||||
#include "common/list.h"
|
|
||||||
#include "memorypool.h"
|
|
||||||
#include "common/str.h"
|
|
||||||
#include "common/util.h"
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
MemoryPool *g_refCountPool = 0; // FIXME: This is never freed right now
|
|
||||||
|
|
||||||
static uint32 computeCapacity(uint32 len) {
|
|
||||||
// By default, for the capacity we use the next multiple of 32
|
|
||||||
return ((len + 32 - 1) & ~0x1F);
|
|
||||||
}
|
|
||||||
|
|
||||||
String::String(const char *str) : _size(0), _str(_storage) {
|
|
||||||
if (str == 0) {
|
|
||||||
_storage[0] = 0;
|
|
||||||
_size = 0;
|
|
||||||
} else
|
|
||||||
initWithCStr(str, strlen(str));
|
|
||||||
}
|
|
||||||
|
|
||||||
String::String(const char *str, uint32 len) : _size(0), _str(_storage) {
|
|
||||||
initWithCStr(str, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
String::String(const char *beginP, const char *endP) : _size(0), _str(_storage) {
|
|
||||||
assert(endP >= beginP);
|
|
||||||
initWithCStr(beginP, endP - beginP);
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::initWithCStr(const char *str, uint32 len) {
|
|
||||||
assert(str);
|
|
||||||
|
|
||||||
// Init _storage member explicitly (ie. without calling its constructor)
|
|
||||||
// for GCC 2.95.x compatibility (see also tracker item #1602879).
|
|
||||||
_storage[0] = 0;
|
|
||||||
|
|
||||||
_size = len;
|
|
||||||
|
|
||||||
if (len >= _builtinCapacity) {
|
|
||||||
// Not enough internal storage, so allocate more
|
|
||||||
_extern._capacity = computeCapacity(len+1);
|
|
||||||
_extern._refCount = 0;
|
|
||||||
_str = new char[_extern._capacity];
|
|
||||||
assert(_str != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the string into the storage area
|
|
||||||
memmove(_str, str, len);
|
|
||||||
_str[len] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
String::String(const String &str)
|
|
||||||
: _size(str._size) {
|
|
||||||
if (str.isStorageIntern()) {
|
|
||||||
// String in internal storage: just copy it
|
|
||||||
memcpy(_storage, str._storage, _builtinCapacity);
|
|
||||||
_str = _storage;
|
|
||||||
} else {
|
|
||||||
// String in external storage: use refcount mechanism
|
|
||||||
str.incRefCount();
|
|
||||||
_extern._refCount = str._extern._refCount;
|
|
||||||
_extern._capacity = str._extern._capacity;
|
|
||||||
_str = str._str;
|
|
||||||
}
|
|
||||||
assert(_str != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
String::String(char c)
|
|
||||||
: _size(0), _str(_storage) {
|
|
||||||
|
|
||||||
_storage[0] = c;
|
|
||||||
_storage[1] = 0;
|
|
||||||
|
|
||||||
_size = (c == 0) ? 0 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
String::~String() {
|
|
||||||
decRefCount(_extern._refCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::makeUnique() {
|
|
||||||
ensureCapacity(_size, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure that enough storage is available to store at least new_size
|
|
||||||
* characters plus a null byte. In addition, if we currently share
|
|
||||||
* the storage with another string, unshare it, so that we can safely
|
|
||||||
* write to the storage.
|
|
||||||
*/
|
|
||||||
void String::ensureCapacity(uint32 new_size, bool keep_old) {
|
|
||||||
bool isShared;
|
|
||||||
uint32 curCapacity, newCapacity;
|
|
||||||
char *newStorage;
|
|
||||||
int *oldRefCount = _extern._refCount;
|
|
||||||
|
|
||||||
if (isStorageIntern()) {
|
|
||||||
isShared = false;
|
|
||||||
curCapacity = _builtinCapacity;
|
|
||||||
} else {
|
|
||||||
isShared = (oldRefCount && *oldRefCount > 1);
|
|
||||||
curCapacity = _extern._capacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case: If there is enough space, and we do not share
|
|
||||||
// the storage, then there is nothing to do.
|
|
||||||
if (!isShared && new_size < curCapacity)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (isShared && new_size < _builtinCapacity) {
|
|
||||||
// We share the storage, but there is enough internal storage: Use that.
|
|
||||||
newStorage = _storage;
|
|
||||||
newCapacity = _builtinCapacity;
|
|
||||||
} else {
|
|
||||||
// We need to allocate storage on the heap!
|
|
||||||
|
|
||||||
// Compute a suitable new capacity limit
|
|
||||||
// If the current capacity is sufficient we use the same capacity
|
|
||||||
if (new_size < curCapacity)
|
|
||||||
newCapacity = curCapacity;
|
|
||||||
else
|
|
||||||
newCapacity = MAX(curCapacity * 2, computeCapacity(new_size+1));
|
|
||||||
|
|
||||||
// Allocate new storage
|
|
||||||
newStorage = new char[newCapacity];
|
|
||||||
assert(newStorage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy old data if needed, elsewise reset the new storage.
|
|
||||||
if (keep_old) {
|
|
||||||
assert(_size < newCapacity);
|
|
||||||
memcpy(newStorage, _str, _size + 1);
|
|
||||||
} else {
|
|
||||||
_size = 0;
|
|
||||||
newStorage[0] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release hold on the old storage ...
|
|
||||||
decRefCount(oldRefCount);
|
|
||||||
|
|
||||||
// ... in favor of the new storage
|
|
||||||
_str = newStorage;
|
|
||||||
|
|
||||||
if (!isStorageIntern()) {
|
|
||||||
// Set the ref count & capacity if we use an external storage.
|
|
||||||
// It is important to do this *after* copying any old content,
|
|
||||||
// else we would override data that has not yet been copied!
|
|
||||||
_extern._refCount = 0;
|
|
||||||
_extern._capacity = newCapacity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::incRefCount() const {
|
|
||||||
assert(!isStorageIntern());
|
|
||||||
if (_extern._refCount == 0) {
|
|
||||||
if (g_refCountPool == 0) {
|
|
||||||
g_refCountPool = new MemoryPool(sizeof(int));
|
|
||||||
assert(g_refCountPool);
|
|
||||||
}
|
|
||||||
|
|
||||||
_extern._refCount = (int *)g_refCountPool->allocChunk();
|
|
||||||
*_extern._refCount = 2;
|
|
||||||
} else {
|
|
||||||
++(*_extern._refCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::decRefCount(int *oldRefCount) {
|
|
||||||
if (isStorageIntern())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (oldRefCount) {
|
|
||||||
--(*oldRefCount);
|
|
||||||
}
|
|
||||||
if (!oldRefCount || *oldRefCount <= 0) {
|
|
||||||
// The ref count reached zero, so we free the string storage
|
|
||||||
// and the ref count storage.
|
|
||||||
if (oldRefCount) {
|
|
||||||
assert(g_refCountPool);
|
|
||||||
g_refCountPool->freeChunk(oldRefCount);
|
|
||||||
}
|
|
||||||
delete[] _str;
|
|
||||||
|
|
||||||
// Even though _str points to a freed memory block now,
|
|
||||||
// we do not change its value, because any code that calls
|
|
||||||
// decRefCount will have to do this afterwards anyway.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String &String::operator=(const char *str) {
|
|
||||||
uint32 len = strlen(str);
|
|
||||||
ensureCapacity(len, false);
|
|
||||||
_size = len;
|
|
||||||
memmove(_str, str, len + 1);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
String &String::operator=(const String &str) {
|
|
||||||
if (&str == this)
|
|
||||||
return *this;
|
|
||||||
|
|
||||||
if (str.isStorageIntern()) {
|
|
||||||
decRefCount(_extern._refCount);
|
|
||||||
_size = str._size;
|
|
||||||
_str = _storage;
|
|
||||||
memcpy(_str, str._str, _size + 1);
|
|
||||||
} else {
|
|
||||||
str.incRefCount();
|
|
||||||
decRefCount(_extern._refCount);
|
|
||||||
|
|
||||||
_extern._refCount = str._extern._refCount;
|
|
||||||
_extern._capacity = str._extern._capacity;
|
|
||||||
_size = str._size;
|
|
||||||
_str = str._str;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
String &String::operator=(char c) {
|
|
||||||
decRefCount(_extern._refCount);
|
|
||||||
_str = _storage;
|
|
||||||
|
|
||||||
_str[0] = c;
|
|
||||||
_str[1] = 0;
|
|
||||||
|
|
||||||
_size = (c == 0) ? 0 : 1;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
String &String::operator+=(const char *str) {
|
|
||||||
if (_str <= str && str <= _str + _size)
|
|
||||||
return operator+=(String(str));
|
|
||||||
|
|
||||||
int len = strlen(str);
|
|
||||||
if (len > 0) {
|
|
||||||
ensureCapacity(_size + len, true);
|
|
||||||
|
|
||||||
memcpy(_str + _size, str, len + 1);
|
|
||||||
_size += len;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
String &String::operator+=(const String &str) {
|
|
||||||
if (&str == this)
|
|
||||||
return operator+=(String(str));
|
|
||||||
|
|
||||||
int len = str._size;
|
|
||||||
if (len > 0) {
|
|
||||||
ensureCapacity(_size + len, true);
|
|
||||||
|
|
||||||
memcpy(_str + _size, str._str, len + 1);
|
|
||||||
_size += len;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
String &String::operator+=(char c) {
|
|
||||||
ensureCapacity(_size + 1, true);
|
|
||||||
|
|
||||||
_str[_size++] = c;
|
|
||||||
_str[_size] = 0;
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::hasPrefix(const String &x) const {
|
|
||||||
return hasPrefix(x.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::hasPrefix(const char *x) const {
|
|
||||||
assert(x != 0);
|
|
||||||
// Compare x with the start of _str.
|
|
||||||
const char *y = c_str();
|
|
||||||
while (*x && *x == *y) {
|
|
||||||
++x;
|
|
||||||
++y;
|
|
||||||
}
|
|
||||||
// It's a prefix, if and only if all letters in x are 'used up' before
|
|
||||||
// _str ends.
|
|
||||||
return *x == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::hasSuffix(const String &x) const {
|
|
||||||
return hasSuffix(x.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::hasSuffix(const char *x) const {
|
|
||||||
assert(x != 0);
|
|
||||||
// Compare x with the end of _str.
|
|
||||||
const uint32 x_size = strlen(x);
|
|
||||||
if (x_size > _size)
|
|
||||||
return false;
|
|
||||||
const char *y = c_str() + _size - x_size;
|
|
||||||
while (*x && *x == *y) {
|
|
||||||
++x;
|
|
||||||
++y;
|
|
||||||
}
|
|
||||||
// It's a suffix, if and only if all letters in x are 'used up' before
|
|
||||||
// _str ends.
|
|
||||||
return *x == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::contains(const String &x) const {
|
|
||||||
return strstr(c_str(), x.c_str()) != NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::contains(const char *x) const {
|
|
||||||
assert(x != 0);
|
|
||||||
return strstr(c_str(), x) != NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::contains(char x) const {
|
|
||||||
return strchr(c_str(), x) != NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::deleteLastChar() {
|
|
||||||
if (_size > 0)
|
|
||||||
deleteChar(_size - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::deleteChar(uint32 p) {
|
|
||||||
assert(p < _size);
|
|
||||||
|
|
||||||
makeUnique();
|
|
||||||
while (p++ < _size)
|
|
||||||
_str[p - 1] = _str[p];
|
|
||||||
_size--;
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::erase(uint32 p, uint32 len) {
|
|
||||||
assert(p < _size);
|
|
||||||
|
|
||||||
makeUnique();
|
|
||||||
// If len == npos or p + len is over the end, remove all the way to the end
|
|
||||||
if (len == npos || p + len >= _size) {
|
|
||||||
// Delete char at p as well. So _size = (p - 1) + 1
|
|
||||||
_size = p;
|
|
||||||
// Null terminate
|
|
||||||
_str[_size] = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for ( ; p + len <= _size; p++) {
|
|
||||||
_str[p] = _str[p + len];
|
|
||||||
}
|
|
||||||
_size -= len;
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::clear() {
|
|
||||||
decRefCount(_extern._refCount);
|
|
||||||
|
|
||||||
_size = 0;
|
|
||||||
_str = _storage;
|
|
||||||
_storage[0] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::setChar(char c, uint32 p) {
|
|
||||||
assert(p < _size);
|
|
||||||
|
|
||||||
makeUnique();
|
|
||||||
_str[p] = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::insertChar(char c, uint32 p) {
|
|
||||||
assert(p <= _size);
|
|
||||||
|
|
||||||
ensureCapacity(_size + 1, true);
|
|
||||||
_size++;
|
|
||||||
for (uint32 i = _size; i > p; --i)
|
|
||||||
_str[i] = _str[i - 1];
|
|
||||||
_str[p] = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::toLowercase() {
|
|
||||||
makeUnique();
|
|
||||||
for (uint32 i = 0; i < _size; ++i)
|
|
||||||
_str[i] = tolower(_str[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::toUppercase() {
|
|
||||||
makeUnique();
|
|
||||||
for (uint32 i = 0; i < _size; ++i)
|
|
||||||
_str[i] = toupper(_str[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint String::hash() const {
|
|
||||||
return hashit(c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
String String::format(const char *fmt, ...) {
|
|
||||||
String output;
|
|
||||||
|
|
||||||
va_list va;
|
|
||||||
va_start(va, fmt);
|
|
||||||
output = String::vformat(fmt, va);
|
|
||||||
va_end(va);
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
String String::vformat(const char *fmt, va_list args) {
|
|
||||||
String output;
|
|
||||||
assert(output.isStorageIntern());
|
|
||||||
|
|
||||||
va_list va;
|
|
||||||
scumm_va_copy(va, args);
|
|
||||||
int len = vsnprintf(output._str, _builtinCapacity, fmt, va);
|
|
||||||
va_end(va);
|
|
||||||
|
|
||||||
if (len == -1 || len == _builtinCapacity - 1) {
|
|
||||||
// MSVC and IRIX don't return the size the full string would take up.
|
|
||||||
// MSVC returns -1, IRIX returns the number of characters actually written,
|
|
||||||
// which is at the most the size of the buffer minus one, as the string is
|
|
||||||
// truncated to fit.
|
|
||||||
|
|
||||||
// We assume MSVC failed to output the correct, null-terminated string
|
|
||||||
// if the return value is either -1 or size.
|
|
||||||
// For IRIX, because we lack a better mechanism, we assume failure
|
|
||||||
// if the return value equals size - 1.
|
|
||||||
// The downside to this is that whenever we try to format a string where the
|
|
||||||
// size is 1 below the built-in capacity, the size is needlessly increased.
|
|
||||||
|
|
||||||
// Try increasing the size of the string until it fits.
|
|
||||||
int size = _builtinCapacity;
|
|
||||||
do {
|
|
||||||
size *= 2;
|
|
||||||
output.ensureCapacity(size - 1, false);
|
|
||||||
assert(!output.isStorageIntern());
|
|
||||||
size = output._extern._capacity;
|
|
||||||
|
|
||||||
scumm_va_copy(va, args);
|
|
||||||
len = vsnprintf(output._str, size, fmt, va);
|
|
||||||
va_end(va);
|
|
||||||
} while (len == -1 || len >= size - 1);
|
|
||||||
output._size = len;
|
|
||||||
} else if (len < (int)_builtinCapacity) {
|
|
||||||
// vsnprintf succeeded
|
|
||||||
output._size = len;
|
|
||||||
} else {
|
|
||||||
// vsnprintf didn't have enough space, so grow buffer
|
|
||||||
output.ensureCapacity(len, false);
|
|
||||||
scumm_va_copy(va, args);
|
|
||||||
int len2 = vsnprintf(output._str, len+1, fmt, va);
|
|
||||||
va_end(va);
|
|
||||||
assert(len == len2);
|
|
||||||
output._size = len2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark -
|
|
||||||
|
|
||||||
bool String::operator==(const String &x) const {
|
|
||||||
return equals(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::operator==(const char *x) const {
|
|
||||||
assert(x != 0);
|
|
||||||
return equals(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::operator!=(const String &x) const {
|
|
||||||
return !equals(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::operator !=(const char *x) const {
|
|
||||||
assert(x != 0);
|
|
||||||
return !equals(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::operator<(const String &x) const {
|
|
||||||
return compareTo(x) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::operator<=(const String &x) const {
|
|
||||||
return compareTo(x) <= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::operator>(const String &x) const {
|
|
||||||
return compareTo(x) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::operator>=(const String &x) const {
|
|
||||||
return compareTo(x) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark -
|
|
||||||
|
|
||||||
bool operator==(const char* y, const String &x) {
|
|
||||||
return (x == y);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator!=(const char* y, const String &x) {
|
|
||||||
return x != y;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark -
|
|
||||||
|
|
||||||
bool String::equals(const String &x) const {
|
|
||||||
return (0 == compareTo(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::equals(const char *x) const {
|
|
||||||
assert(x != 0);
|
|
||||||
return (0 == compareTo(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::equalsIgnoreCase(const String &x) const {
|
|
||||||
return (0 == compareToIgnoreCase(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::equalsIgnoreCase(const char *x) const {
|
|
||||||
assert(x != 0);
|
|
||||||
return (0 == compareToIgnoreCase(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
int String::compareTo(const String &x) const {
|
|
||||||
return compareTo(x.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
int String::compareTo(const char *x) const {
|
|
||||||
assert(x != 0);
|
|
||||||
return strcmp(c_str(), x);
|
|
||||||
}
|
|
||||||
|
|
||||||
int String::compareToIgnoreCase(const String &x) const {
|
|
||||||
return compareToIgnoreCase(x.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
int String::compareToIgnoreCase(const char *x) const {
|
|
||||||
assert(x != 0);
|
|
||||||
return scumm_stricmp(c_str(), x);
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark -
|
|
||||||
|
|
||||||
String operator+(const String &x, const String &y) {
|
|
||||||
String temp(x);
|
|
||||||
temp += y;
|
|
||||||
return temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
String operator+(const char *x, const String &y) {
|
|
||||||
String temp(x);
|
|
||||||
temp += y;
|
|
||||||
return temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
String operator+(const String &x, const char *y) {
|
|
||||||
String temp(x);
|
|
||||||
temp += y;
|
|
||||||
return temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
String operator+(char x, const String &y) {
|
|
||||||
String temp(x);
|
|
||||||
temp += y;
|
|
||||||
return temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
String operator+(const String &x, char y) {
|
|
||||||
String temp(x);
|
|
||||||
temp += y;
|
|
||||||
return temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
String lastPathComponent(const String &path, const char sep) {
|
|
||||||
const char *str = path.c_str();
|
|
||||||
const char *last = str + path.size();
|
|
||||||
|
|
||||||
// Skip over trailing slashes
|
|
||||||
while (last > str && *(last-1) == sep)
|
|
||||||
--last;
|
|
||||||
|
|
||||||
// Path consisted of only slashes -> return empty string
|
|
||||||
if (last == str)
|
|
||||||
return String();
|
|
||||||
|
|
||||||
// Now scan the whole component
|
|
||||||
const char *first = last - 1;
|
|
||||||
while (first > str && *first != sep)
|
|
||||||
--first;
|
|
||||||
|
|
||||||
if (*first == sep)
|
|
||||||
first++;
|
|
||||||
|
|
||||||
return String(first, last);
|
|
||||||
}
|
|
||||||
|
|
||||||
String normalizePath(const String &path, const char sep) {
|
|
||||||
if (path.empty())
|
|
||||||
return path;
|
|
||||||
|
|
||||||
const char *cur = path.c_str();
|
|
||||||
String result;
|
|
||||||
|
|
||||||
// If there is a leading slash, preserve that:
|
|
||||||
if (*cur == sep) {
|
|
||||||
result += sep;
|
|
||||||
// Skip over multiple leading slashes, so "//" equals "/"
|
|
||||||
while (*cur == sep)
|
|
||||||
++cur;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan for path components till the end of the String
|
|
||||||
List<String> comps;
|
|
||||||
while (*cur != 0) {
|
|
||||||
const char *start = cur;
|
|
||||||
|
|
||||||
// Scan till the next path separator resp. the end of the string
|
|
||||||
while (*cur != sep && *cur != 0)
|
|
||||||
cur++;
|
|
||||||
|
|
||||||
const String component(start, cur);
|
|
||||||
|
|
||||||
if (component.empty() || component == ".") {
|
|
||||||
// Skip empty components and dot components
|
|
||||||
} else if (!comps.empty() && component == ".." && comps.back() != "..") {
|
|
||||||
// If stack is non-empty and top is not "..", remove top
|
|
||||||
comps.pop_back();
|
|
||||||
} else {
|
|
||||||
// Add the component to the stack
|
|
||||||
comps.push_back(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip over separator chars
|
|
||||||
while (*cur == sep)
|
|
||||||
cur++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, assemble all components back into a path
|
|
||||||
while (!comps.empty()) {
|
|
||||||
result += comps.front();
|
|
||||||
comps.pop_front();
|
|
||||||
if (!comps.empty())
|
|
||||||
result += sep;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t strlcpy(char *dst, const char *src, size_t size) {
|
|
||||||
// Our backup of the source's start, we need this
|
|
||||||
// to calculate the source's length.
|
|
||||||
const char * const srcStart = src;
|
|
||||||
|
|
||||||
// In case a non-empty size was specified we
|
|
||||||
// copy over (size - 1) bytes at max.
|
|
||||||
if (size != 0) {
|
|
||||||
// Copy over (size - 1) bytes at max.
|
|
||||||
while (--size != 0) {
|
|
||||||
if ((*dst++ = *src) == 0)
|
|
||||||
break;
|
|
||||||
++src;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case the source string was longer than the
|
|
||||||
// destination, we need to add a terminating
|
|
||||||
// zero.
|
|
||||||
if (size == 0)
|
|
||||||
*dst = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to the terminating zero of the source
|
|
||||||
// string, we need this to determine the length
|
|
||||||
// of the source string.
|
|
||||||
while (*src)
|
|
||||||
++src;
|
|
||||||
|
|
||||||
// Return the source string's length.
|
|
||||||
return src - srcStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t strlcat(char *dst, const char *src, size_t size) {
|
|
||||||
// In case the destination buffer does not contain
|
|
||||||
// space for at least 1 character, we will just
|
|
||||||
// return the source string's length.
|
|
||||||
if (size == 0)
|
|
||||||
return strlen(src);
|
|
||||||
|
|
||||||
// Our backup of the source's start, we need this
|
|
||||||
// to calculate the source's length.
|
|
||||||
const char * const srcStart = src;
|
|
||||||
|
|
||||||
// Our backup of the destination's start, we need
|
|
||||||
// this to calculate the destination's length.
|
|
||||||
const char * const dstStart = dst;
|
|
||||||
|
|
||||||
// Search the end of the destination, but do not
|
|
||||||
// move past the terminating zero.
|
|
||||||
while (size-- != 0 && *dst != 0)
|
|
||||||
++dst;
|
|
||||||
|
|
||||||
// Calculate the destination's length;
|
|
||||||
const size_t dstLength = dst - dstStart;
|
|
||||||
|
|
||||||
// In case we reached the end of the destination
|
|
||||||
// buffer before we had a chance to append any
|
|
||||||
// characters we will just return the destination
|
|
||||||
// length plus the source string's length.
|
|
||||||
if (size == 0)
|
|
||||||
return dstLength + strlen(srcStart);
|
|
||||||
|
|
||||||
// Copy over all of the source that fits
|
|
||||||
// the destination buffer. We also need
|
|
||||||
// to take the terminating zero we will
|
|
||||||
// add into consideration.
|
|
||||||
while (size-- != 0 && *src != 0)
|
|
||||||
*dst++ = *src++;
|
|
||||||
*dst = 0;
|
|
||||||
|
|
||||||
// Move to the terminating zero of the source
|
|
||||||
// string, we need this to determine the length
|
|
||||||
// of the source string.
|
|
||||||
while (*src)
|
|
||||||
++src;
|
|
||||||
|
|
||||||
// Return the total length of the result string
|
|
||||||
return dstLength + (src - srcStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // End of namespace Common
|
|
||||||
|
|
||||||
// Portable implementation of stricmp / strcasecmp / strcmpi.
|
|
||||||
// TODO: Rename this to Common::strcasecmp
|
|
||||||
int scumm_stricmp(const char *s1, const char *s2) {
|
|
||||||
byte l1, l2;
|
|
||||||
do {
|
|
||||||
// Don't use ++ inside tolower, in case the macro uses its
|
|
||||||
// arguments more than once.
|
|
||||||
l1 = (byte)*s1++;
|
|
||||||
l1 = tolower(l1);
|
|
||||||
l2 = (byte)*s2++;
|
|
||||||
l2 = tolower(l2);
|
|
||||||
} while (l1 == l2 && l1 != 0);
|
|
||||||
return l1 - l2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Portable implementation of strnicmp / strncasecmp / strncmpi.
|
|
||||||
// TODO: Rename this to Common::strncasecmp
|
|
||||||
int scumm_strnicmp(const char *s1, const char *s2, uint n) {
|
|
||||||
byte l1, l2;
|
|
||||||
do {
|
|
||||||
if (n-- == 0)
|
|
||||||
return 0; // no difference found so far -> signal equality
|
|
||||||
|
|
||||||
// Don't use ++ inside tolower, in case the macro uses its
|
|
||||||
// arguments more than once.
|
|
||||||
l1 = (byte)*s1++;
|
|
||||||
l1 = tolower(l1);
|
|
||||||
l2 = (byte)*s2++;
|
|
||||||
l2 = tolower(l2);
|
|
||||||
} while (l1 == l2 && l1 != 0);
|
|
||||||
return l1 - l2;
|
|
||||||
}
|
|
|
@ -1,386 +0,0 @@
|
||||||
/* ScummVM - Graphic Adventure Engine
|
|
||||||
*
|
|
||||||
* ScummVM is the legal property of its developers, whose names
|
|
||||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
||||||
* file distributed with this source distribution.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef COMMON_STRING_H
|
|
||||||
#define COMMON_STRING_H
|
|
||||||
|
|
||||||
#include "common/scummsys.h"
|
|
||||||
|
|
||||||
#include <stdarg.h>
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple string class for ScummVM. Provides automatic storage managment,
|
|
||||||
* and overloads several operators in a 'natural' fashion, mimicking
|
|
||||||
* the std::string class. Even provides simple iterators.
|
|
||||||
*
|
|
||||||
* This class tries to avoid allocating lots of small blocks on the heap,
|
|
||||||
* since that is inefficient on several platforms supported by ScummVM.
|
|
||||||
* Instead, small strings are stored 'inside' the string object (i.e. on
|
|
||||||
* the stack, for stack allocated objects), and only for strings exceeding
|
|
||||||
* a certain length do we allocate a buffer on the heap.
|
|
||||||
*
|
|
||||||
* The presence of \0 characters in the string will cause undefined
|
|
||||||
* behavior in some operations.
|
|
||||||
*/
|
|
||||||
class String {
|
|
||||||
public:
|
|
||||||
static const uint32 npos = 0xFFFFFFFF;
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* The size of the internal storage. Increasing this means less heap
|
|
||||||
* allocations are needed, at the cost of more stack memory usage,
|
|
||||||
* and of course lots of wasted memory. Empirically, 90% or more of
|
|
||||||
* all String instances are less than 32 chars long. If a platform
|
|
||||||
* is very short on stack space, it would be possible to lower this.
|
|
||||||
* A value of 24 still seems acceptable, though considerably worse,
|
|
||||||
* while 16 seems to be the lowest you want to go... Anything lower
|
|
||||||
* than 8 makes no sense, since that's the size of member _extern
|
|
||||||
* (on 32 bit machines; 12 bytes on systems with 64bit pointers).
|
|
||||||
*/
|
|
||||||
static const uint32 _builtinCapacity = 32 - sizeof(uint32) - sizeof(char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Length of the string. Stored to avoid having to call strlen
|
|
||||||
* a lot. Yes, we limit ourselves to strings shorter than 4GB --
|
|
||||||
* on purpose :-).
|
|
||||||
*/
|
|
||||||
uint32 _size;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pointer to the actual string storage. Either points to _storage,
|
|
||||||
* or to a block allocated on the heap via malloc.
|
|
||||||
*/
|
|
||||||
char *_str;
|
|
||||||
|
|
||||||
|
|
||||||
union {
|
|
||||||
/**
|
|
||||||
* Internal string storage.
|
|
||||||
*/
|
|
||||||
char _storage[_builtinCapacity];
|
|
||||||
/**
|
|
||||||
* External string storage data -- the refcounter, and the
|
|
||||||
* capacity of the string _str points to.
|
|
||||||
*/
|
|
||||||
struct {
|
|
||||||
mutable int *_refCount;
|
|
||||||
uint32 _capacity;
|
|
||||||
} _extern;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline bool isStorageIntern() const {
|
|
||||||
return _str == _storage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
/** Construct a new empty string. */
|
|
||||||
String() : _size(0), _str(_storage) { _storage[0] = 0; }
|
|
||||||
|
|
||||||
/** Construct a new string from the given NULL-terminated C string. */
|
|
||||||
String(const char *str);
|
|
||||||
|
|
||||||
/** Construct a new string containing exactly len characters read from address str. */
|
|
||||||
String(const char *str, uint32 len);
|
|
||||||
|
|
||||||
/** Construct a new string containing the characters between beginP (including) and endP (excluding). */
|
|
||||||
String(const char *beginP, const char *endP);
|
|
||||||
|
|
||||||
/** Construct a copy of the given string. */
|
|
||||||
String(const String &str);
|
|
||||||
|
|
||||||
/** Construct a string consisting of the given character. */
|
|
||||||
explicit String(char c);
|
|
||||||
|
|
||||||
~String();
|
|
||||||
|
|
||||||
String &operator=(const char *str);
|
|
||||||
String &operator=(const String &str);
|
|
||||||
String &operator=(char c);
|
|
||||||
String &operator+=(const char *str);
|
|
||||||
String &operator+=(const String &str);
|
|
||||||
String &operator+=(char c);
|
|
||||||
|
|
||||||
bool operator==(const String &x) const;
|
|
||||||
bool operator==(const char *x) const;
|
|
||||||
bool operator!=(const String &x) const;
|
|
||||||
bool operator!=(const char *x) const;
|
|
||||||
|
|
||||||
bool operator<(const String &x) const;
|
|
||||||
bool operator<=(const String &x) const;
|
|
||||||
bool operator>(const String &x) const;
|
|
||||||
bool operator>=(const String &x) const;
|
|
||||||
|
|
||||||
bool equals(const String &x) const;
|
|
||||||
bool equalsIgnoreCase(const String &x) const;
|
|
||||||
int compareTo(const String &x) const; // strcmp clone
|
|
||||||
int compareToIgnoreCase(const String &x) const; // stricmp clone
|
|
||||||
|
|
||||||
bool equals(const char *x) const;
|
|
||||||
bool equalsIgnoreCase(const char *x) const;
|
|
||||||
int compareTo(const char *x) const; // strcmp clone
|
|
||||||
int compareToIgnoreCase(const char *x) const; // stricmp clone
|
|
||||||
|
|
||||||
bool hasSuffix(const String &x) const;
|
|
||||||
bool hasSuffix(const char *x) const;
|
|
||||||
|
|
||||||
bool hasPrefix(const String &x) const;
|
|
||||||
bool hasPrefix(const char *x) const;
|
|
||||||
|
|
||||||
bool contains(const String &x) const;
|
|
||||||
bool contains(const char *x) const;
|
|
||||||
bool contains(char x) const;
|
|
||||||
|
|
||||||
inline const char *c_str() const { return _str; }
|
|
||||||
inline uint size() const { return _size; }
|
|
||||||
|
|
||||||
inline bool empty() const { return (_size == 0); }
|
|
||||||
char firstChar() const { return (_size > 0) ? _str[0] : 0; }
|
|
||||||
char lastChar() const { return (_size > 0) ? _str[_size - 1] : 0; }
|
|
||||||
|
|
||||||
char operator[](int idx) const {
|
|
||||||
assert(_str && idx >= 0 && idx < (int)_size);
|
|
||||||
return _str[idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Remove the last character from the string. */
|
|
||||||
void deleteLastChar();
|
|
||||||
|
|
||||||
/** Remove the character at position p from the string. */
|
|
||||||
void deleteChar(uint32 p);
|
|
||||||
|
|
||||||
/** Remove all characters from position p to the p + len. If len = String::npos, removes all characters to the end */
|
|
||||||
void erase(uint32 p, uint32 len = npos);
|
|
||||||
|
|
||||||
/** Set character c at position p, replacing the previous character there. */
|
|
||||||
void setChar(char c, uint32 p);
|
|
||||||
|
|
||||||
/** Insert character c before position p. */
|
|
||||||
void insertChar(char c, uint32 p);
|
|
||||||
|
|
||||||
/** Clears the string, making it empty. */
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
/** Convert all characters in the string to lowercase. */
|
|
||||||
void toLowercase();
|
|
||||||
|
|
||||||
/** Convert all characters in the string to uppercase. */
|
|
||||||
void toUppercase();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes trailing and leading whitespaces. Uses isspace() to decide
|
|
||||||
* what is whitespace and what not.
|
|
||||||
*/
|
|
||||||
void trim();
|
|
||||||
|
|
||||||
uint hash() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Print formatted data into a String object. Similar to sprintf,
|
|
||||||
* except that it stores the result in (variably sized) String
|
|
||||||
* instead of a fixed size buffer.
|
|
||||||
*/
|
|
||||||
static String format(const char *fmt, ...) GCC_PRINTF(1,2);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Print formatted data into a String object. Similar to vsprintf,
|
|
||||||
* except that it stores the result in (variably sized) String
|
|
||||||
* instead of a fixed size buffer.
|
|
||||||
*/
|
|
||||||
static String vformat(const char *fmt, va_list args);
|
|
||||||
|
|
||||||
public:
|
|
||||||
typedef char value_type;
|
|
||||||
/**
|
|
||||||
* Unsigned version of the underlying type. This can be used to cast
|
|
||||||
* individual string characters to bigger integer types without sign
|
|
||||||
* extension happening.
|
|
||||||
*/
|
|
||||||
typedef unsigned char unsigned_type;
|
|
||||||
typedef char * iterator;
|
|
||||||
typedef const char * const_iterator;
|
|
||||||
|
|
||||||
iterator begin() {
|
|
||||||
// Since the user could potentially
|
|
||||||
// change the string via the returned
|
|
||||||
// iterator we have to assure we are
|
|
||||||
// pointing to a unique storage.
|
|
||||||
makeUnique();
|
|
||||||
|
|
||||||
return _str;
|
|
||||||
}
|
|
||||||
|
|
||||||
iterator end() {
|
|
||||||
return begin() + size();
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator begin() const {
|
|
||||||
return _str;
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator end() const {
|
|
||||||
return begin() + size();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void makeUnique();
|
|
||||||
void ensureCapacity(uint32 new_size, bool keep_old);
|
|
||||||
void incRefCount() const;
|
|
||||||
void decRefCount(int *oldRefCount);
|
|
||||||
void initWithCStr(const char *str, uint32 len);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Append two strings to form a new (temp) string
|
|
||||||
String operator+(const String &x, const String &y);
|
|
||||||
|
|
||||||
String operator+(const char *x, const String &y);
|
|
||||||
String operator+(const String &x, const char *y);
|
|
||||||
|
|
||||||
String operator+(const String &x, char y);
|
|
||||||
String operator+(char x, const String &y);
|
|
||||||
|
|
||||||
// Some useful additional comparison operators for Strings
|
|
||||||
bool operator==(const char *x, const String &y);
|
|
||||||
bool operator!=(const char *x, const String &y);
|
|
||||||
|
|
||||||
// Utility functions to remove leading and trailing whitespaces
|
|
||||||
extern char *ltrim(char *t);
|
|
||||||
extern char *rtrim(char *t);
|
|
||||||
extern char *trim(char *t);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the last component of a given path.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
* /foo/bar.txt would return 'bar.txt'
|
|
||||||
* /foo/bar/ would return 'bar'
|
|
||||||
* /foo/./bar// would return 'bar'
|
|
||||||
*
|
|
||||||
* @param path the path of which we want to know the last component
|
|
||||||
* @param sep character used to separate path components
|
|
||||||
* @return The last component of the path.
|
|
||||||
*/
|
|
||||||
String lastPathComponent(const String &path, const char sep);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalize a given path to a canonical form. In particular:
|
|
||||||
* - trailing separators are removed: /foo/bar/ -> /foo/bar
|
|
||||||
* - double separators (= empty components) are removed: /foo//bar -> /foo/bar
|
|
||||||
* - dot components are removed: /foo/./bar -> /foo/bar
|
|
||||||
*
|
|
||||||
* @todo remove double dot components: /foo/baz/../bar -> /foo/bar
|
|
||||||
*
|
|
||||||
* @param path the path to normalize
|
|
||||||
* @param sep the separator token (usually '/' on Unix-style systems, or '\\' on Windows based stuff)
|
|
||||||
* @return the normalized path
|
|
||||||
*/
|
|
||||||
String normalizePath(const String &path, const char sep);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple DOS-style pattern matching function (understands * and ? like used in DOS).
|
|
||||||
* Taken from exult/files/listfiles.cc
|
|
||||||
*
|
|
||||||
* Token meaning:
|
|
||||||
* "*": any character, any amount of times.
|
|
||||||
* "?": any character, only once.
|
|
||||||
* "#": any decimal digit, only once.
|
|
||||||
*
|
|
||||||
* Example strings/patterns:
|
|
||||||
* String: monkey.s01 Pattern: monkey.s?? => true
|
|
||||||
* String: monkey.s101 Pattern: monkey.s?? => false
|
|
||||||
* String: monkey.s99 Pattern: monkey.s?1 => false
|
|
||||||
* String: monkey.s101 Pattern: monkey.s* => true
|
|
||||||
* String: monkey.s99 Pattern: monkey.s*1 => false
|
|
||||||
* String: monkey.s01 Pattern: monkey.s## => true
|
|
||||||
* String: monkey.s01 Pattern: monkey.### => false
|
|
||||||
*
|
|
||||||
* @param str Text to be matched against the given pattern.
|
|
||||||
* @param pat Glob pattern.
|
|
||||||
* @param ignoreCase Whether to ignore the case when doing pattern match
|
|
||||||
* @param pathMode Whether to use path mode, i.e., whether slashes must be matched explicitly.
|
|
||||||
*
|
|
||||||
* @return true if str matches the pattern, false otherwise.
|
|
||||||
*/
|
|
||||||
bool matchString(const char *str, const char *pat, bool ignoreCase = false, bool pathMode = false);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Take a 32 bit value and turn it into a four character string, where each of
|
|
||||||
* the four bytes is turned into one character. Most significant byte is printed
|
|
||||||
* first.
|
|
||||||
*/
|
|
||||||
String tag2string(uint32 tag);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy up to size - 1 characters from src to dst and also zero terminate the
|
|
||||||
* result. Note that src must be a zero terminated string.
|
|
||||||
*
|
|
||||||
* In case size is zero this function just returns the length of the source
|
|
||||||
* string.
|
|
||||||
*
|
|
||||||
* @note This is modeled after OpenBSD's strlcpy. See the manpage here:
|
|
||||||
* http://www.openbsd.org/cgi-bin/man.cgi?query=strlcpy
|
|
||||||
*
|
|
||||||
* @param dst The destination buffer.
|
|
||||||
* @param src The source string.
|
|
||||||
* @param size The size of the destination buffer.
|
|
||||||
* @return The length of the (non-truncated) result, i.e. strlen(src).
|
|
||||||
*/
|
|
||||||
size_t strlcpy(char *dst, const char *src, size_t size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append the string src to the string dst. Note that both src and dst must be
|
|
||||||
* zero terminated. The result will be zero terminated. At most
|
|
||||||
* "size - strlen(dst) - 1" bytes will be appended.
|
|
||||||
*
|
|
||||||
* In case the dst string does not contain a zero within the first "size" bytes
|
|
||||||
* the dst string will not be changed and size + strlen(src) is returned.
|
|
||||||
*
|
|
||||||
* @note This is modeled after OpenBSD's strlcat. See the manpage here:
|
|
||||||
* http://www.openbsd.org/cgi-bin/man.cgi?query=strlcat
|
|
||||||
*
|
|
||||||
* @param dst The string the source string should be appended to.
|
|
||||||
* @param src The source string.
|
|
||||||
* @param size The (total) size of the destination buffer.
|
|
||||||
* @return The length of the (non-truncated) result. That is
|
|
||||||
* strlen(dst) + strlen(src). In case strlen(dst) > size
|
|
||||||
* size + strlen(src) is returned.
|
|
||||||
*/
|
|
||||||
size_t strlcat(char *dst, const char *src, size_t size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience wrapper for tag2string which "returns" a C string.
|
|
||||||
* Note: It is *NOT* safe to do anything with the return value other than directly
|
|
||||||
* copying or printing it.
|
|
||||||
*/
|
|
||||||
#define tag2str(x) Common::tag2string(x).c_str()
|
|
||||||
|
|
||||||
|
|
||||||
} // End of namespace Common
|
|
||||||
|
|
||||||
extern int scumm_stricmp(const char *s1, const char *s2);
|
|
||||||
extern int scumm_strnicmp(const char *s1, const char *s2, uint n);
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,40 +0,0 @@
|
||||||
/* ScummVM - Graphic Adventure Engine
|
|
||||||
*
|
|
||||||
* ScummVM is the legal property of its developers, whose names
|
|
||||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
||||||
* file distributed with this source distribution.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Disable symbol overrides so that we can use system headers.
|
|
||||||
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
|
||||||
|
|
||||||
// HACK to allow building with the SDL backend on MinGW
|
|
||||||
// see bug #1800764 "TOOLS: MinGW tools building broken"
|
|
||||||
#ifdef main
|
|
||||||
#undef main
|
|
||||||
#endif // main
|
|
||||||
|
|
||||||
#include "ultima1_map.h"
|
|
||||||
#include "file.h"
|
|
||||||
|
|
||||||
void writeUltima1EnhancedMap(Archive &a) {
|
|
||||||
Common::MemFile map;
|
|
||||||
map.writeString("TEST");
|
|
||||||
|
|
||||||
a.add("ULTIMA1ENH/MAP", map);
|
|
||||||
}
|
|
|
@ -20,22 +20,15 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Disable symbol overrides so that we can use system headers.
|
#include "create_ultima.h"
|
||||||
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
|
||||||
|
|
||||||
// HACK to allow building with the SDL backend on MinGW
|
|
||||||
// see bug #1800764 "TOOLS: MinGW tools building broken"
|
|
||||||
#ifdef main
|
|
||||||
#undef main
|
|
||||||
#endif // main
|
|
||||||
|
|
||||||
#include "ultima1_resources.h"
|
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
|
|
||||||
#define FILE_BUFFER_SIZE 1024
|
#define FILE_BUFFER_SIZE 1024
|
||||||
#define MD5_COMPUTE_SIZE 1024
|
#define MD5_COMPUTE_SIZE 1024
|
||||||
|
|
||||||
uint32 computeMD5(Common::File &f) {
|
typedef unsigned int uint32;
|
||||||
|
|
||||||
|
uint32 computeMD5(File &f) {
|
||||||
uint32 total = 0;
|
uint32 total = 0;
|
||||||
f.seek(0);
|
f.seek(0);
|
||||||
for (int idx = 0; idx < MD5_COMPUTE_SIZE; ++idx)
|
for (int idx = 0; idx < MD5_COMPUTE_SIZE; ++idx)
|
||||||
|
@ -51,8 +44,12 @@ uint32 computeMD5(Common::File &f) {
|
||||||
#define LOGO_WIDTH2 76
|
#define LOGO_WIDTH2 76
|
||||||
#define LOGO_TABLE1 0x3262
|
#define LOGO_TABLE1 0x3262
|
||||||
#define LOGO_TABLE2 0x4320
|
#define LOGO_TABLE2 0x4320
|
||||||
|
#define FLAG_WIDTH 8
|
||||||
|
#define FLAG_HEIGHT 8
|
||||||
|
#define NUM_FLAGS 3
|
||||||
|
|
||||||
void createLogo(Common::File &in, Common::MemFile &out) {
|
// Creates the logo.bmp file
|
||||||
|
void createLogo(File &in) {
|
||||||
int offsets[LOGO_HEIGHT][2];
|
int offsets[LOGO_HEIGHT][2];
|
||||||
char buffer[LOGO_WIDTH1 + LOGO_WIDTH2 + 1];
|
char buffer[LOGO_WIDTH1 + LOGO_WIDTH2 + 1];
|
||||||
|
|
||||||
|
@ -64,6 +61,11 @@ void createLogo(Common::File &in, Common::MemFile &out) {
|
||||||
for (int y = 0; y < LOGO_HEIGHT; ++y)
|
for (int y = 0; y < LOGO_HEIGHT; ++y)
|
||||||
offsets[y][1] = in.readWord();
|
offsets[y][1] = in.readWord();
|
||||||
|
|
||||||
|
// Set up a surface
|
||||||
|
Surface out(LOGO_WIDTH1 + LOGO_WIDTH2, LOGO_HEIGHT);
|
||||||
|
out.setPaletteEntry(0, 0, 0, 0);
|
||||||
|
out.setPaletteEntry(1, 0xff, 0xff, 0xff);
|
||||||
|
|
||||||
// Convert the lines
|
// Convert the lines
|
||||||
for (int y = 0; y < LOGO_HEIGHT; ++y) {
|
for (int y = 0; y < LOGO_HEIGHT; ++y) {
|
||||||
in.seek(DATA_SEGMENT_OFFSET + offsets[y][0]);
|
in.seek(DATA_SEGMENT_OFFSET + offsets[y][0]);
|
||||||
|
@ -74,30 +76,40 @@ void createLogo(Common::File &in, Common::MemFile &out) {
|
||||||
in.read(buffer + LOGO_WIDTH1, LOGO_WIDTH2 + 1);
|
in.read(buffer + LOGO_WIDTH1, LOGO_WIDTH2 + 1);
|
||||||
assert(buffer[LOGO_WIDTH1 + LOGO_WIDTH2] == '\0');
|
assert(buffer[LOGO_WIDTH1 + LOGO_WIDTH2] == '\0');
|
||||||
|
|
||||||
for (int x = 0; x < (LOGO_WIDTH1 + LOGO_WIDTH2); ++x)
|
byte *line = out.getBasePtr(0, y);
|
||||||
out.writeByte(buffer[x] == '*' ? 1 : 0);
|
for (int x = 0; x < (LOGO_WIDTH1 + LOGO_WIDTH2); ++x, ++line)
|
||||||
|
*line = buffer[x] == '*' ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out.saveToFile("logo.bmp");
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeUltima1Resources(Archive &a) {
|
// Creates the flags.bmp file
|
||||||
|
void createFlags(File &in) {
|
||||||
|
Surface out(FLAG_WIDTH * NUM_FLAGS, FLAG_HEIGHT);
|
||||||
|
out.setPaletteEntry(10, 0, 0, 0);
|
||||||
|
out.setPaletteEntry(11, 0xff, 0xff, 0xff);
|
||||||
|
|
||||||
|
in.seek(DATA_SEGMENT_OFFSET + 0x124);
|
||||||
|
|
||||||
|
for (int flagNum = 0; flagNum < NUM_FLAGS; ++flagNum) {
|
||||||
|
for (int yp = 0; yp < FLAG_HEIGHT; ++yp) {
|
||||||
|
byte *line = out.getBasePtr(flagNum * FLAG_WIDTH, yp);
|
||||||
|
for (int xp = 0; xp < FLAG_WIDTH; ++xp)
|
||||||
|
*line++ = in.readByte() ? 10 : 11;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.saveToFile("flags.bmp");
|
||||||
|
}
|
||||||
|
|
||||||
|
void extractUltima1Resources() {
|
||||||
// Open up ultima1.exe for logo
|
// Open up ultima1.exe for logo
|
||||||
Common::File u1;
|
File u1("ultima.exe");
|
||||||
if (!u1.open("Ultima 1/ultima.exe"))
|
|
||||||
error("Could not find ultima.exe");
|
|
||||||
if (computeMD5(u1) != 64620)
|
if (computeMD5(u1) != 64620)
|
||||||
error("Unknown version of Ultima 1 ultima.exe");
|
error("Unknown version of Ultima 1 ultima.exe");
|
||||||
|
|
||||||
// Add the Origin logo
|
// Extract the Origin logo and flag animation data
|
||||||
Common::MemFile logo;
|
createLogo(u1);
|
||||||
createLogo(u1, logo);
|
createFlags(u1);
|
||||||
a.add("ULTIMA1/LOGO", logo);
|
|
||||||
|
|
||||||
// Add the flag data for the castle title screen
|
|
||||||
Common::MemFile flags;
|
|
||||||
u1.seek(DATA_SEGMENT_OFFSET + 0x124);
|
|
||||||
for (int idx = 0; idx < 64 * 3; ++idx)
|
|
||||||
flags.writeByte(u1.readByte() ? 10 : 11);
|
|
||||||
a.add("ULTIMA1/FLAGS", flags);
|
|
||||||
|
|
||||||
u1.close();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,6 @@
|
||||||
#ifndef ULTIMA1_RESOURCES_H
|
#ifndef ULTIMA1_RESOURCES_H
|
||||||
#define ULTIMA1_RESOURCES_H
|
#define ULTIMA1_RESOURCES_H
|
||||||
|
|
||||||
#include "archive.h"
|
extern void extractUltima1Resources();
|
||||||
|
|
||||||
extern void writeUltima1Resources(Archive &a);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue