mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-31 16:03:24 +00:00
ULTIMA1: Beginnings of create_ultima tool
This commit is contained in:
parent
75bf34d7da
commit
18c88beb9d
83
devtools/create_ultima/archive.cpp
Normal file
83
devtools/create_ultima/archive.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
/* 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(0);
|
||||
|
||||
// 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);
|
||||
}
|
||||
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()));
|
||||
}
|
90
devtools/create_ultima/archive.h
Normal file
90
devtools/create_ultima/archive.h
Normal file
@ -0,0 +1,90 @@
|
||||
/* 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
|
55
devtools/create_ultima/create_ultima.cpp
Normal file
55
devtools/create_ultima/create_ultima.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
/* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "archive.h"
|
||||
#include "ultima1_map.h"
|
||||
|
||||
#define VERSION_NUMBER 1
|
||||
|
||||
void NORETURN_PRE error(const char *s, ...) {
|
||||
printf("%s\n", s);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
Archive archive;
|
||||
if (!archive.open("ultima.dat")) {
|
||||
error("Could not open output file");
|
||||
}
|
||||
|
||||
writeUltima1EnhancedMap(archive);
|
||||
|
||||
archive.close();
|
||||
return 0;
|
||||
}
|
273
devtools/create_ultima/file.h
Normal file
273
devtools/create_ultima/file.h
Normal file
@ -0,0 +1,273 @@
|
||||
/* 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 __FILE_H__
|
||||
#define __FILE_H__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/util.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
enum AccessMode {
|
||||
kFileReadMode = 1,
|
||||
kFileWriteMode = 2
|
||||
};
|
||||
|
||||
class Stream {
|
||||
public:
|
||||
Stream() {}
|
||||
virtual ~Stream() {}
|
||||
|
||||
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) {
|
||||
for (size_t idx = 0; idx < len; ++idx)
|
||||
writeByte(src.readByte());
|
||||
}
|
||||
byte readByte() {
|
||||
byte v;
|
||||
read(&v, sizeof(byte));
|
||||
return v;
|
||||
}
|
||||
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() {
|
||||
uint16 v;
|
||||
read(&v, sizeof(uint16));
|
||||
return FROM_BE_16(v);
|
||||
}
|
||||
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) {
|
||||
write(&v, sizeof(byte));
|
||||
}
|
||||
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 {
|
||||
private:
|
||||
::FILE *_f;
|
||||
public:
|
||||
File() : _f(nullptr) {}
|
||||
virtual ~File() { close(); }
|
||||
|
||||
bool open(const char *filename, AccessMode mode = kFileReadMode) {
|
||||
_f = fopen(filename, (mode == kFileReadMode) ? "rb" : "wb+");
|
||||
return (_f != NULL);
|
||||
}
|
||||
void close() {
|
||||
if (_f)
|
||||
fclose(_f);
|
||||
_f = nullptr;
|
||||
}
|
||||
|
||||
virtual int seek(int offset, int whence = SEEK_SET) {
|
||||
return fseek(_f, offset, whence);
|
||||
}
|
||||
virtual long read(void *buffer, size_t len) {
|
||||
return fread(buffer, 1, len, _f);
|
||||
}
|
||||
virtual void write(const void *buffer, size_t len) {
|
||||
assert(_f);
|
||||
fwrite(buffer, 1, len, _f);
|
||||
}
|
||||
virtual uint pos() const {
|
||||
return ftell(_f);
|
||||
}
|
||||
virtual uint size() const {
|
||||
uint currentPos = pos();
|
||||
fseek(_f, 0, SEEK_END);
|
||||
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
|
||||
|
||||
class MemFile : public Stream {
|
||||
private:
|
||||
byte _data[MAX_MEM_SIZE];
|
||||
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() {
|
||||
memset(_data, 0, MAX_MEM_SIZE);
|
||||
_size = _offset = 0;
|
||||
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 uint pos() const {
|
||||
return _offset;
|
||||
}
|
||||
virtual uint size() const {
|
||||
return _size;
|
||||
}
|
||||
virtual bool eof() const {
|
||||
return _offset >= _size;
|
||||
}
|
||||
|
||||
const byte *getData() const { return _data; }
|
||||
|
||||
void syncString(const char *str) {
|
||||
write(str, strlen(str) + 1);
|
||||
}
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
} // End of namespace Common
|
||||
|
||||
#endif
|
86
devtools/create_ultima/hash-str.h
Normal file
86
devtools/create_ultima/hash-str.h
Normal file
@ -0,0 +1,86 @@
|
||||
/* 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
|
109
devtools/create_ultima/hashmap.cpp
Normal file
109
devtools/create_ultima/hashmap.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
/* 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
|
637
devtools/create_ultima/hashmap.h
Normal file
637
devtools/create_ultima/hashmap.h
Normal file
@ -0,0 +1,637 @@
|
||||
/* 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
|
182
devtools/create_ultima/memorypool.cpp
Normal file
182
devtools/create_ultima/memorypool.cpp
Normal file
@ -0,0 +1,182 @@
|
||||
/* 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
|
162
devtools/create_ultima/memorypool.h
Normal file
162
devtools/create_ultima/memorypool.h
Normal file
@ -0,0 +1,162 @@
|
||||
/* 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
|
15
devtools/create_ultima/module.mk
Normal file
15
devtools/create_ultima/module.mk
Normal file
@ -0,0 +1,15 @@
|
||||
MODULE := devtools/create_ultima
|
||||
|
||||
MODULE_OBJS := \
|
||||
create_ultima.o \
|
||||
archive.o \
|
||||
ultima1_map.o \
|
||||
hashmap.o \
|
||||
memorypool.o \
|
||||
str.o
|
||||
|
||||
# Set the name of the executable
|
||||
TOOL_EXECUTABLE := create_ultima
|
||||
|
||||
# Include common rules
|
||||
include $(srcdir)/rules.mk
|
786
devtools/create_ultima/str.cpp
Normal file
786
devtools/create_ultima/str.cpp
Normal file
@ -0,0 +1,786 @@
|
||||
/* 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;
|
||||
}
|
386
devtools/create_ultima/str.h
Normal file
386
devtools/create_ultima/str.h
Normal file
@ -0,0 +1,386 @@
|
||||
/* 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
|
40
devtools/create_ultima/ultima1_map.cpp
Normal file
40
devtools/create_ultima/ultima1_map.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
/* 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);
|
||||
}
|
30
devtools/create_ultima/ultima1_map.h
Normal file
30
devtools/create_ultima/ultima1_map.h
Normal file
@ -0,0 +1,30 @@
|
||||
/* 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 CONSTANTS_H
|
||||
#define CONSTANTS_H
|
||||
|
||||
#include "archive.h"
|
||||
|
||||
extern void writeUltima1EnhancedMap(Archive &a);
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user