/* 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 SCI_RESOURCE_H #define SCI_RESOURCE_H #include "common/str.h" #include "common/list.h" #include "common/hashmap.h" #include "sci/graphics/helpers.h" // for ViewType #include "sci/decompressor.h" #include "sci/sci.h" #include "sci/util.h" namespace Common { class File; class FSList; class FSNode; class WriteStream; class SeekableReadStream; } namespace Sci { enum { #ifdef ENABLE_SCI32 // Hack to treat RESMAP.PAT/RESSCI.PAT as the highest volume kResPatVolumeNumber = 100, #endif kResourceHeaderSize = 2, ///< patch type + header size /** The maximum allowed size for a compressed or decompressed resource */ SCI_MAX_RESOURCE_SIZE = 0x0400000 }; /** Resource status types */ enum ResourceStatus { kResStatusNoMalloc = 0, kResStatusAllocated, kResStatusEnqueued, /**< In the LRU queue */ kResStatusLocked /**< Allocated and in use */ }; /** Resource error codes. Should be in sync with s_errorDescriptions */ enum ResourceErrorCodes { SCI_ERROR_NONE = 0, SCI_ERROR_IO_ERROR = 1, SCI_ERROR_EMPTY_RESOURCE = 2, SCI_ERROR_RESMAP_INVALID_ENTRY = 3, /**< Invalid resource.map entry */ SCI_ERROR_RESMAP_NOT_FOUND = 4, SCI_ERROR_NO_RESOURCE_FILES_FOUND = 5, /**< No resource at all was found */ SCI_ERROR_UNKNOWN_COMPRESSION = 6, SCI_ERROR_DECOMPRESSION_ERROR = 7, /**< sanity checks failed during decompression */ SCI_ERROR_RESOURCE_TOO_BIG = 8 /**< Resource size exceeds SCI_MAX_RESOURCE_SIZE */ }; enum { MAX_OPENED_VOLUMES = 5 ///< Max number of simultaneously opened volumes }; enum ResourceType { kResourceTypeView = 0, kResourceTypePic, kResourceTypeScript, kResourceTypeText, kResourceTypeSound, kResourceTypeMemory, kResourceTypeVocab, kResourceTypeFont, kResourceTypeCursor, kResourceTypePatch, kResourceTypeBitmap, kResourceTypePalette, kResourceTypeCdAudio = 12, #ifdef ENABLE_SCI32 kResourceTypeWave = 12, #endif kResourceTypeAudio, kResourceTypeSync, kResourceTypeMessage, kResourceTypeMap, kResourceTypeHeap, kResourceTypeAudio36, kResourceTypeSync36, kResourceTypeTranslation, // Currently unsupported // SCI2.1+ Resources kResourceTypeRobot, kResourceTypeVMD, kResourceTypeChunk, kResourceTypeAnimation, // SCI3 Resources kResourceTypeEtc, kResourceTypeDuck, kResourceTypeClut, kResourceTypeTGA, kResourceTypeZZZ, // Mac-only resources kResourceTypeMacIconBarPictN, // IBIN resources (icon bar, not selected) kResourceTypeMacIconBarPictS, // IBIS resources (icon bar, selected) kResourceTypeMacPict, // PICT resources (inventory) kResourceTypeRave, // KQ6 hires RAVE (special sync) resources kResourceTypeInvalid }; const char *getResourceTypeName(ResourceType restype); const char *getResourceTypeExtension(ResourceType restype); enum ResVersion { kResVersionUnknown, kResVersionSci0Sci1Early, kResVersionSci1Middle, kResVersionKQ5FMT, kResVersionSci1Late, kResVersionSci11, kResVersionSci11Mac, kResVersionSci2, kResVersionSci3 }; class ResourceManager; class ResourceSource; class ResourceId { static inline ResourceType fixupType(ResourceType type) { if (type >= kResourceTypeInvalid) return kResourceTypeInvalid; return type; } ResourceType _type; uint16 _number; uint32 _tuple; // Only used for audio36 and sync36 static Common::String intToBase36(uint32 number, int minChar) { // Convert from an integer to a base36 string Common::String string; while (minChar--) { int character = number % 36; string = ((character < 10) ? (character + '0') : (character + 'A' - 10)) + string; number /= 36; } return string; } friend void syncWithSerializer(Common::Serializer &s, ResourceId &obj); public: ResourceId() : _type(kResourceTypeInvalid), _number(0), _tuple(0) { } ResourceId(ResourceType type_, uint16 number_, uint32 tuple_ = 0) : _type(fixupType(type_)), _number(number_), _tuple(tuple_) { } ResourceId(ResourceType type_, uint16 number_, byte noun, byte verb, byte cond, byte seq) : _type(fixupType(type_)), _number(number_) { _tuple = (noun << 24) | (verb << 16) | (cond << 8) | seq; } Common::String toString() const { Common::String retStr = Common::String::format("%s.%d", getResourceTypeName(_type), _number); if (_tuple != 0) { retStr += Common::String::format("(%d, %d, %d, %d)", _tuple >> 24, (_tuple >> 16) & 0xff, (_tuple >> 8) & 0xff, _tuple & 0xff); } return retStr; } // Convert from a resource ID to a base36 patch name Common::String toPatchNameBase36() const { Common::String output; if (getSciVersion() >= SCI_VERSION_2) { output += (getType() == kResourceTypeAudio36) ? 'A' : 'S'; // Identifier } else { output += (getType() == kResourceTypeAudio36) ? '@' : '#'; // Identifier } output += intToBase36(getNumber(), 3); // Map output += intToBase36(getTuple() >> 24, 2); // Noun output += intToBase36((getTuple() >> 16) & 0xff, 2); // Verb output += '.'; // Separator output += intToBase36((getTuple() >> 8) & 0xff, 2); // Cond output += intToBase36(getTuple() & 0xff, 1); // Seq assert(output.size() == 12); // We should always get 12 characters in the end return output; } inline ResourceType getType() const { return _type; } inline uint16 getNumber() const { return _number; } inline uint32 getTuple() const { return _tuple; } inline uint hash() const { return ((uint)((_type << 16) | _number)) ^ _tuple; } bool operator==(const ResourceId &other) const { return (_type == other._type) && (_number == other._number) && (_tuple == other._tuple); } bool operator!=(const ResourceId &other) const { return !operator==(other); } bool operator<(const ResourceId &other) const { return (_type < other._type) || ((_type == other._type) && (_number < other._number)) || ((_type == other._type) && (_number == other._number) && (_tuple < other._tuple)); } }; struct ResourceIdHash : public Common::UnaryFunction { uint operator()(ResourceId val) const { return val.hash(); } }; /** Class for storing resources in memory */ class Resource : public SciSpan { friend class ResourceManager; // FIXME: These 'friend' declarations are meant to be a temporary hack to // ease transition to the ResourceSource class system. friend class ResourceSource; friend class PatchResourceSource; friend class WaveResourceSource; friend class AudioVolumeResourceSource; friend class MacResourceForkResourceSource; #ifdef ENABLE_SCI32 friend class ChunkResourceSource; #endif protected: /** * Holds the extra header data from view, pic, and palette patches so that * these patches can be rewritten to disk as valid patch files by the * `diskdump` debugger command. */ byte *_header; uint32 _headerSize; public: Resource(ResourceManager *resMan, ResourceId id); ~Resource(); void unalloc(); inline ResourceType getType() const { return _id.getType(); } inline uint16 getNumber() const { return _id.getNumber(); } bool isLocked() const { return _status == kResStatusLocked; } /** * Write the resource to the specified stream. * This method is used only by the "dump" debugger command. */ void writeToStream(Common::WriteStream *stream) const; #ifdef ENABLE_SCI32 Common::SeekableReadStream *makeStream() const; #endif const Common::String &getResourceLocation() const; // FIXME: This audio specific method is a hack. After all, why should a // Resource have audio specific methods? But for now we keep this, as it // eases transition. uint32 getAudioCompressionType() const; uint16 getNumLockers() const { return _lockers; } protected: ResourceId _id; // TODO: _id could almost be made const, only readResourceInfo() modifies it... int32 _fileOffset; /**< Offset in file */ ResourceStatus _status; uint16 _lockers; /**< Number of places where this resource was locked */ ResourceSource *_source; ResourceManager *_resMan; bool loadPatch(Common::SeekableReadStream *file); bool loadFromPatchFile(); bool loadFromWaveFile(Common::SeekableReadStream *file); bool loadFromAudioVolumeSCI1(Common::SeekableReadStream *file); bool loadFromAudioVolumeSCI11(Common::SeekableReadStream *file); int decompress(ResVersion volVersion, Common::SeekableReadStream *file); int readResourceInfo(ResVersion volVersion, Common::SeekableReadStream *file, uint32 &szPacked, ResourceCompression &compression); }; typedef Common::HashMap ResourceMap; class IntMapResourceSource; class ResourceManager { // FIXME: These 'friend' declarations are meant to be a temporary hack to // ease transition to the ResourceSource class system. friend class ResourceSource; friend class DirectoryResourceSource; friend class PatchResourceSource; friend class ExtMapResourceSource; friend class IntMapResourceSource; friend class AudioVolumeResourceSource; friend class ExtAudioMapResourceSource; friend class WaveResourceSource; friend class MacResourceForkResourceSource; #ifdef ENABLE_SCI32 friend class ChunkResourceSource; #endif public: /** * Creates a new SCI resource manager. */ ResourceManager(const bool detectionMode = false); ~ResourceManager(); /** * Initializes the resource manager. */ void init(); /** * Adds all of the resource files for a game */ int addAppropriateSources(); /** * Similar to the function above, only called from the fallback detector */ int addAppropriateSourcesForDetection(const Common::FSList &fslist); // TODO: Switch from FSList to Common::Archive? /** * Looks up a resource's data. * @param id The resource type to look for * @param lock non-zero iff the resource should be locked * @return The resource, or NULL if it doesn't exist * @note Locked resources are guaranteed not to have their contents freed until * they are unlocked explicitly (by unlockResource). */ Resource *findResource(ResourceId id, bool lock); /** * Unlocks a previously locked resource. * @param res The resource to free */ void unlockResource(Resource *res); /** * Tests whether a resource exists. * * This function may often be much faster than finding the resource * and should be preferred for simple tests. * The resource object returned is, indeed, the resource in question, but * it should be used with care, as it may be unallocated. * Use scir_find_resource() if you want to use the data contained in the resource. * * @param id Id of the resource to check * @return non-NULL if the resource exists, NULL otherwise */ Resource *testResource(ResourceId id); /** * Returns a list of all resources of the specified type. * @param type The resource type to look for * @param mapNumber For audio36 and sync36, limit search to this map * @return The resource list */ Common::List listResources(ResourceType type, int mapNumber = -1); void setAudioLanguage(int language); int getAudioLanguage() const; void changeAudioDirectory(Common::String path); bool isGMTrackIncluded(); bool isSci11Mac() const { return _volVersion == kResVersionSci11Mac; } ViewType getViewType() const { return _viewType; } const char *getMapVersionDesc() const { return versionDescription(_mapVersion); } const char *getVolVersionDesc() const { return versionDescription(_volVersion); } ResVersion getVolVersion() const { return _volVersion; } /** * Adds the appropriate GM patch from the Sierra MIDI utility as 4.pat, without * requiring the user to rename the file to 4.pat. Thus, the original Sierra * archive can be extracted in the extras directory, and the GM patches can be * applied per game, if applicable. */ void addNewGMPatch(SciGameId gameId); #ifdef ENABLE_SCI32 /** * Parses all resources from a SCI2.1 chunk resource and adds them to the * resource manager. */ void addResourcesFromChunk(uint16 id); /** * Updates the currently active disc number. */ void findDisc(const int16 discNo); /** * Gets the currently active disc number. */ int16 getCurrentDiscNo() const { return _currentDiscNo; } private: /** * The currently active disc number. */ int16 _currentDiscNo; /** * If true, the game has multiple audio volumes that contain different * audio files for each disc. */ bool _multiDiscAudio; public: #endif // Detects, if standard font of current game includes extended characters (>0x80) bool detectFontExtended(); // Detects, if SCI1.1 game uses palette merging bool detectPaletteMergingSci11(); // Detects, if SCI0EARLY game also has SCI0EARLY sound resources bool detectEarlySound(); /** * Finds the internal Sierra ID of the current game from script 0. */ Common::String findSierraGameId(const bool isBE); /** * Finds the location of the game object from script 0. * @param addSci11ScriptOffset Adjust the return value for SCI1.1 and newer * games. Needs to be false when the heap is accessed directly inside * findSierraGameId(). */ reg_t findGameObject(const bool addSci11ScriptOffset, const bool isBE); /** * Converts a map resource type to our type * @param sciType The type from the map/patch * @return The ResourceType */ ResourceType convertResType(byte type); protected: bool _detectionMode; // Maximum number of bytes to allow being allocated for resources // Note: maxMemory will not be interpreted as a hard limit, only as a restriction // for resources which are not explicitly locked. However, a warning will be // issued whenever this limit is exceeded. int _maxMemoryLRU; ViewType _viewType; // Used to determine if the game has EGA or VGA graphics typedef Common::List SourcesList; SourcesList _sources; int _memoryLocked; ///< Amount of resource bytes in locked memory int _memoryLRU; ///< Amount of resource bytes under LRU control Common::List _LRU; ///< Last Resource Used list ResourceMap _resMap; Common::List _volumeFiles; ///< list of opened volume files ResourceSource *_audioMapSCI1; ///< Currently loaded audio map for SCI1 ResVersion _volVersion; ///< resource.0xx version ResVersion _mapVersion; ///< resource.map version /** * Add a path to the resource manager's list of sources. * @return a pointer to the added source structure, or NULL if an error occurred. */ ResourceSource *addPatchDir(const Common::String &path); ResourceSource *findVolume(ResourceSource *map, int volume_nr); /** * Adds a source to the resource manager's list of sources. * @param source The new source to add * @return A pointer to the added source structure, or NULL if an error occurred. */ ResourceSource *addSource(ResourceSource *source); /** * Add an external (i.e., separate file) map resource to the resource * manager's list of sources. * @param filename The name of the volume to add * @param volume_nr The volume number the map starts at, 0 for data; uint16 curPos; long time; byte prev; Channel() : number(0), flags(0), poly(0), prio(0), data(), curPos(0) { time = 0; prev = 0; } }; struct Track { byte type; byte channelCount; SciSpan header; Channel *channels; int16 digitalChannelNr; uint16 digitalSampleRate; uint16 digitalSampleSize; uint16 digitalSampleStart; uint16 digitalSampleEnd; }; public: SoundResource(uint32 resNumber, ResourceManager *resMan, SciVersion soundVersion); ~SoundResource(); #if 0 Track *getTrackByNumber(uint16 number); #endif Track *getTrackByType(byte type); Track *getDigitalTrack(); int getChannelFilterMask(int hardwareMask, bool wantsRhythm); byte getInitialVoiceCount(byte channel); byte getSoundPriority() const { return _soundPriority; } private: SciVersion _soundVersion; int _trackCount; Track *_tracks; Resource *_innerResource; ResourceManager *_resMan; byte _soundPriority; }; } // End of namespace Sci #endif // SCI_RESOURCE_H