diff options
132 files changed, 14868 insertions, 1432 deletions
diff --git a/backends/fs/psp/psp-stream.h b/backends/fs/psp/psp-stream.h index 9fd1ad0470..673630b685 100644 --- a/backends/fs/psp/psp-stream.h +++ b/backends/fs/psp/psp-stream.h @@ -35,39 +35,25 @@ */ class PSPIoStream : public StdioStream, public Suspendable { protected: - Common::String _path; - int _fileSize; - bool _writeMode; // for resuming in the right mode - int _physicalPos; // position in the real file - int _pos; // position. Sometimes virtual - bool _inCache; // whether we're in cache (virtual) mode - bool _eos; // EOS flag - + Common::String _path; /* Need to maintain for reopening after suspend */ + bool _writeMode; /* "" */ + int _pos; /* "" */ + mutable int _ferror; /* Save file ferror */ + mutable bool _feof; /* and eof */ + enum { SuspendError = 2, ResumeError = 3 }; - enum { - CACHE_SIZE = 1024, - MIN_READ_SIZE = 1024 // reading less than 1024 takes exactly the same time as 1024 - }; - - // For caching - char *_cache; - int _cacheStartOffset; // starting offset of the cache. -1 when cache is invalid - - mutable int _ferror; // file error state - int _errorSuspend; // for debugging + int _errorSuspend; mutable int _errorSource; + + // Error checking int _errorPos; void * _errorHandle; int _suspendCount; - bool synchronizePhysicalPos(); // synchronize the physical and virtual positions - bool isOffsetInCache(uint32 pos); // check if an offset is found in cache - bool isCacheValid() { return _cacheStartOffset != -1; } - public: /** diff --git a/backends/module.mk b/backends/module.mk index e0a3f4c683..c0457d8062 100644 --- a/backends/module.mk +++ b/backends/module.mk @@ -22,6 +22,12 @@ MODULE_OBJS := \ midi/timidity.o \ midi/dmedia.o \ midi/windows.o \ + plugins/elf-loader.o \ + plugins/mips-loader.o \ + plugins/shorts-segment-manager.o \ + plugins/arm-loader.o \ + plugins/elf-provider.o \ + plugins/dc/dc-provider.o \ plugins/posix/posix-provider.o \ plugins/sdl/sdl-provider.o \ plugins/win32/win32-provider.o \ @@ -43,7 +49,8 @@ endif ifeq ($(BACKEND),ds) MODULE_OBJS += \ fs/ds/ds-fs-factory.o \ - fs/ds/ds-fs.o + fs/ds/ds-fs.o \ + plugins/ds/ds-provider.o endif ifeq ($(BACKEND),n64) @@ -54,7 +61,8 @@ endif ifeq ($(BACKEND),ps2) MODULE_OBJS += \ - fs/ps2/ps2-fs-factory.o + fs/ps2/ps2-fs-factory.o \ + plugins/ps2/ps2-provider.o endif ifeq ($(BACKEND),psp) diff --git a/backends/platform/ds/arm7/source/main.cpp b/backends/platform/ds/arm7/source/main.cpp index bcaaf8e936..75f7012ed0 100644 --- a/backends/platform/ds/arm7/source/main.cpp +++ b/backends/platform/ds/arm7/source/main.cpp @@ -40,7 +40,7 @@ #include <system.h> #include <stdlib.h> #include <string.h> -#include <registers_alt.h> // Needed for SOUND_CR +#include <registers_alt.h> // Needed for SOUND_CR #include <NDS/scummvm_ipc.h> ////////////////////////////////////////////////////////////////////// #ifdef USE_DEBUGGER @@ -592,7 +592,7 @@ int main(int argc, char ** argv) { IPC->reset = false; - //fifoInit(); + //fifoInit(); for (int r = 0; r < 8; r++) { IPC->adpcm.arm7Buffer[r] = (u8 *) malloc(512); diff --git a/backends/platform/ds/arm9/makefile b/backends/platform/ds/arm9/makefile index eedf75c256..c9a665c442 100644 --- a/backends/platform/ds/arm9/makefile +++ b/backends/platform/ds/arm9/makefile @@ -1,10 +1,13 @@ srcdir ?= . DEPDIR := .deps -#DYNAMIC_MODULES = 1 +VERBOSE_BUILD = 1 +DYNAMIC_MODULES = 1 libndsdir = $(DEVKITPRO)/libnds #libndsdir = /home/neil/devkitpror21/libnds +ENABLED = DYNAMIC_PLUGIN + # Select the build by setting SCUMM_BUILD to a,b,c,d,e,f or g. # Anything else gets build a. @@ -12,7 +15,7 @@ ifeq ($(SCUMM_BUILD),k) DS_BUILD_K = 1 else ifeq ($(SCUMM_BUILD),j) - DS_BUILD_J = 1 + DS_BUILD_J = 1 else ifeq ($(SCUMM_BUILD),i) DS_BUILD_I = 1 @@ -64,9 +67,7 @@ ifdef DS_BUILD_F else ifdef DS_BUILD_E # TODO: Inherit the earth uses so much RAM that I have removed libmad in order to - # claw some back. - - + # claw some back. else ifdef DS_BUILD_I @@ -75,7 +76,7 @@ else ifdef DS_BUILD_K else - # USE_MAD = 1 + #USE_MAD = 1 endif endif endif @@ -112,7 +113,7 @@ USE_ARM_COSTUME_ASM = 1 ifdef DS_BUILD_A DEFINES = -DDS_BUILD_A -DUSE_ARM_GFX_ASM -DUSE_ARM_COSTUME_ASM LOGO = logoa.bmp - ENABLE_SCUMM = STATIC_PLUGIN + ENABLE_SCUMM = $(ENABLED) USE_ARM_GFX_ASM = 1 BUILD=scummvm-A endif @@ -120,66 +121,66 @@ endif ifdef DS_BUILD_B DEFINES = -DDS_BUILD_B LOGO = logob.bmp - ENABLE_SKY = STATIC_PLUGIN - ENABLE_QUEEN = STATIC_PLUGIN + ENABLE_SKY = $(ENABLED) + ENABLE_QUEEN = $(ENABLED) BUILD=scummvm-B endif ifdef DS_BUILD_C DEFINES = -DDS_BUILD_C LOGO = logoc.bmp - ENABLE_AGOS = STATIC_PLUGIN + ENABLE_AGOS = $(ENABLED) BUILD=scummvm-C endif ifdef DS_BUILD_D DEFINES = -DDS_BUILD_D LOGO = logod.bmp - ENABLE_GOB = STATIC_PLUGIN - ENABLE_CINE = STATIC_PLUGIN - ENABLE_AGI = STATIC_PLUGIN + ENABLE_GOB = $(ENABLED) + ENABLE_CINE = $(ENABLED) + ENABLE_AGI = $(ENABLED) BUILD=scummvm-D endif ifdef DS_BUILD_E DEFINES = -DDS_BUILD_E LOGO = logoe.bmp - ENABLE_SAGA = STATIC_PLUGIN + ENABLE_SAGA = $(ENABLED) BUILD=scummvm-E endif ifdef DS_BUILD_F DEFINES = -DDS_BUILD_F LOGO = logof.bmp - ENABLE_KYRA = STATIC_PLUGIN + ENABLE_KYRA = $(ENABLED) BUILD=scummvm-F endif ifdef DS_BUILD_G DEFINES = -DDS_BUILD_G LOGO = logog.bmp - ENABLE_LURE = STATIC_PLUGIN + ENABLE_LURE = $(ENABLED) BUILD=scummvm-G endif ifdef DS_BUILD_H DEFINES = -DDS_BUILD_H LOGO = logoh.bmp - ENABLE_PARALLACTION = STATIC_PLUGIN + ENABLE_PARALLACTION = $(ENABLED) BUILD=scummvm-H endif ifdef DS_BUILD_I DEFINES = -DDS_BUILD_I LOGO = logoi.bmp - ENABLE_MADE = STATIC_PLUGIN + ENABLE_MADE = $(ENABLED) BUILD=scummvm-I endif ifdef DS_BUILD_K DEFINES = -DDS_BUILD_K LOGO = logok.bmp - ENABLE_CRUISE = STATIC_PLUGIN + ENABLE_CRUISE = $(ENABLED) BUILD=scummvm-K endif @@ -187,18 +188,18 @@ endif #ifdef DS_BUILD_L # DEFINES = -DDS_BUILD_L # LOGO = logog.bmp -# ENABLE_DRASCULA = STATIC_PLUGIN +# ENABLE_DRASCULA = $(ENABLED) # BUILD=scummvm-K #endif #ifdef DS_BUILD_M # DEFINES = -DDS_BUILD_M # LOGO = logog.bmp -# ENABLE_TUCKER = STATIC_PLUGIN +# ENABLE_TUCKER = $(ENABLED) # BUILD=scummvm-K #endif -ARM7BIN := -7 $(CURDIR)/../../arm7/arm7.bin +ARM7BIN := -7 $(CURDIR)/../arm7/arm7.bin CC = arm-eabi-gcc CXX = arm-eabi-g++ @@ -236,14 +237,14 @@ CXXFLAGS= $(CFLAGS) -Wno-non-virtual-dtor -Wno-unknown-pragmas -Wno-reorder \ ASFLAGS = -mcpu=arm9tdmi -mthumb-interwork -DEFINES += -D__DS__ -DNDS -DARM9 -DNONSTANDARD_PORT -DDISABLE_FANCY_THEMES -DVECTOR_RENDERER_FORMAT=1555 -DDISABLE_DOSBOX_OPL -DDISABLE_DEFAULT_SAVEFILEMANAGER -DARM +DEFINES += -D__DS__ -DNDS -DARM9 -DNONSTANDARD_PORT -DDISABLE_FANCY_THEMES -DVECTOR_RENDERER_FORMAT=1555 -DDISABLE_DOSBOX_OPL -DDISABLE_DEFAULT_SAVEFILEMANAGER -DELF_LOADER_TARGET -DARM -DARM_TARGET#-DNEW_PLUGIN_DESIGN_FIRST_REFINEMENT ifdef USE_MAD DEFINES += -DUSE_MAD endif DEFINES += -DREDUCE_MEMORY_USAGE -LDFLAGS = -specs=ds_arm9.specs -mthumb-interwork -mno-fpu -Wl,-Map,map.txt -Wl,--gc-sections +LDFLAGS = -specs=ds_arm9.specs -mthumb-interwork -mno-fpu -Wl,--target1-abs,-Map,map.txt#-Wl,--gc-sections ifdef WRAP_MALLOC LDFLAGS += -Wl,--wrap,malloc @@ -252,12 +253,11 @@ endif BACKEND := ds -INCLUDES= -I$(portdir)/$(BUILD) -I$(srcdir) -I$(srcdir)/engines \ +INCLUDES= -I$(portdir) -I$(srcdir) -I$(srcdir)/engines \ -I$(portdir)/data -I$(portdir)/../commoninclude \ -I$(portdir)/source -I$(portdir)/source/mad \ -I$(libndsdir)/include -include $(srcdir)/common/scummsys.h - LIBS = -lm -L$(libndsdir)/lib -L$(portdir)/lib -lnds9 ifdef USE_MAD LIBS += -lmad @@ -266,7 +266,19 @@ ifdef USE_DEBUGGER LIBS += -ldsdebugger -ldswifi9 endif +ifeq ($(DYNAMIC_MODULES),1) +DEFINES += -DDYNAMIC_MODULES +PRE_OBJS_FLAGS = -Wl,--whole-archive +POST_OBJS_FLAGS = -Wl,--no-whole-archive +endif + EXECUTABLE = scummvm.elf + +PLUGIN_PREFIX = +PLUGIN_SUFFIX = .plg +PLUGIN_EXTRA_DEPS = $(srcdir)/backends/plugins/ds/plugin.ld $(srcdir)/backends/plugins/plugin.syms $(EXECUTABLE) +PLUGIN_LDFLAGS += -nostartfiles -Wl,-q,--target1-abs,--just-symbols,$(EXECUTABLE),-T$(srcdir)/backends/plugins/ds/plugin.ld,--retain-symbols-file,$(srcdir)/backends/plugins/plugin.syms -lstdc++ -lc -mthumb-interwork -mno-fpu#-Wl,--gc-sections -mno-crt0 $(DEVKITPRO)/devkitARM/arm-eabi/lib/ds_arm9_crt0.o + MKDIR = mkdir -p RM = rm -f RM_REC = rm -rf @@ -340,12 +352,13 @@ OPT_SIZE := -Os -mthumb OBJS := $(DATA_OBJS) $(PORT_OBJS) $(FAT_OBJS) - MODULE_DIRS += . -ndsall: - @[ -d $(BUILD) ] || mkdir -p $(BUILD) - make -C ./$(BUILD) -f ../makefile scummvm.nds scummvm.ds.gba +#ndsall: plugins +# make -f makefile scummvm.nds + +ndsall: plugins + make -f makefile scummvm.nds scummvm.ds.gba include $(srcdir)/Makefile.common @@ -354,11 +367,10 @@ semiclean: clean: $(RM) $(OBJS) $(EXECUTABLE) - rm -fr $(BUILD) + rm -rf *.h engines plugins scummvm.nds scummvm.ds.gba dist : SCUMMVM.BIN plugins plugin_dist - #--------------------------------------------------------------------------------- # canned command sequence for binary data #--------------------------------------------------------------------------------- @@ -400,13 +412,14 @@ endef #--------------------------------------------------------------------------------- %.nds: %.bin - ndstool -c $@ -9 $< $(ARM7BIN) -b ../../$(LOGO) "$(@F);ScummVM $(VERSION);DS Port" + ndstool -c $@ -9 $< $(ARM7BIN) -b ../$(LOGO) "$(@F);ScummVM $(VERSION);DS Port" %.ds.gba: %.nds dsbuild $< -o $@ -l $(portdir)/ndsloader.bin padbin 16 $@ #--------------------------------------------------------------------------------- + %.bin: %.elf $(OBJCOPY) -S -O binary $< $@ diff --git a/backends/platform/ds/arm9/source/dsmain.cpp b/backends/platform/ds/arm9/source/dsmain.cpp index 95bfdfe40a..4c7d6b89ae 100644 --- a/backends/platform/ds/arm9/source/dsmain.cpp +++ b/backends/platform/ds/arm9/source/dsmain.cpp @@ -105,6 +105,8 @@ #endif #include "engine.h" +#include "backends/plugins/ds/ds-provider.h" +#include "backends/plugins/elf-provider.h" #include "backends/fs/ds/ds-fs.h" #include "base/version.h" #include "common/util.h" @@ -3213,6 +3215,9 @@ int main(void) { const char *argv[] = {"/scummvmds"}; #endif +#ifdef DYNAMIC_MODULES + PluginManager::instance().addPluginProvider(new DSPluginProvider()); +#endif while (1) { scummvm_main(ARRAYSIZE(argv), (char **) &argv); diff --git a/backends/platform/ds/arm9/source/fat/io_nmmc.c b/backends/platform/ds/arm9/source/fat/io_nmmc.c index 6c996f5de1..261096a27b 100644 --- a/backends/platform/ds/arm9/source/fat/io_nmmc.c +++ b/backends/platform/ds/arm9/source/fat/io_nmmc.c @@ -170,7 +170,7 @@ bool NMMC_IsInserted(void) { Neo_EnableMMC( true ); // Open SPI port to MMC card Neo_SendMMCCommand(MMC_SEND_CSD, 0); - if( Neo_CheckMMCResponse( 0x00, 0xFF ) == false ) { // Make sure no errors occurred + if( Neo_CheckMMCResponse( 0x00, 0xFF ) == false ) { // Make sure no errors occured Neo_EnableMMC( false ); return false; } @@ -227,14 +227,14 @@ bool NMMC_StartUp(void) { // Set block length Neo_SendMMCCommand(MMC_SET_BLOCKLEN, BYTE_PER_READ ); - if( Neo_CheckMMCResponse( 0x00, 0xFF ) == false ) { // Make sure no errors occurred + if( Neo_CheckMMCResponse( 0x00, 0xFF ) == false ) { // Make sure no errors occured Neo_EnableMMC( false ); return false; } // Check if we can use a higher SPI frequency Neo_SendMMCCommand(MMC_SEND_CSD, 0); - if( Neo_CheckMMCResponse( 0x00, 0xFF ) == false ) { // Make sure no errors occurred + if( Neo_CheckMMCResponse( 0x00, 0xFF ) == false ) { // Make sure no errors occured Neo_EnableMMC( false ); return false; } @@ -268,7 +268,7 @@ bool NMMC_WriteSectors (u32 sector, u8 numSecs, void* buffer) Neo_EnableMMC( true ); // Open SPI port to MMC card Neo_SendMMCCommand( 25, sector ); - if( Neo_CheckMMCResponse( 0x00, 0xFF ) == false ) { // Make sure no errors occurred + if( Neo_CheckMMCResponse( 0x00, 0xFF ) == false ) { // Make sure no errors occured Neo_EnableMMC( false ); return false; } @@ -318,7 +318,7 @@ bool NMMC_ReadSectors (u32 sector, u8 numSecs, void* buffer) while (totalSecs--) { Neo_SendMMCCommand(MMC_READ_BLOCK, sector ); - if( Neo_CheckMMCResponse( 0x00, 0xFF ) == false ) { // Make sure no errors occurred + if( Neo_CheckMMCResponse( 0x00, 0xFF ) == false ) { // Make sure no errors occured Neo_EnableMMC( false ); return false; } diff --git a/backends/platform/gp2x/graphics.cpp b/backends/platform/gp2x/graphics.cpp index 4a3c668c52..1888cbe47c 100644 --- a/backends/platform/gp2x/graphics.cpp +++ b/backends/platform/gp2x/graphics.cpp @@ -1405,6 +1405,7 @@ void OSystem_GP2X::drawMouse() { SDL_Rect zoomdst; SDL_Rect dst; int scale; + int width, height; int hotX, hotY; int tmpScreenWidth, tmpScreenHeight; @@ -1425,12 +1426,16 @@ void OSystem_GP2X::drawMouse() { if (!_overlayVisible) { scale = _videoMode.scaleFactor; + width = _videoMode.screenWidth; + height = _videoMode.screenHeight; dst.w = _mouseCurState.vW; dst.h = _mouseCurState.vH; hotX = _mouseCurState.vHotX; hotY = _mouseCurState.vHotY; } else { scale = 1; + width = _videoMode.overlayWidth; + height = _videoMode.overlayHeight; dst.w = _mouseCurState.rW; dst.h = _mouseCurState.rH; hotX = _mouseCurState.rHotX; diff --git a/backends/platform/gp2xwiz/gp2xwiz-graphics.cpp b/backends/platform/gp2xwiz/gp2xwiz-graphics.cpp index f6ad226d42..921558131a 100644 --- a/backends/platform/gp2xwiz/gp2xwiz-graphics.cpp +++ b/backends/platform/gp2xwiz/gp2xwiz-graphics.cpp @@ -149,6 +149,7 @@ void OSystem_GP2XWIZ::drawMouse() { SDL_Rect dst; int scale; + int width, height; int hotX, hotY; if (_videoMode.mode == GFX_HALF && !_overlayVisible){ @@ -161,12 +162,16 @@ void OSystem_GP2XWIZ::drawMouse() { if (!_overlayVisible) { scale = _videoMode.scaleFactor; + width = _videoMode.screenWidth; + height = _videoMode.screenHeight; dst.w = _mouseCurState.vW; dst.h = _mouseCurState.vH; hotX = _mouseCurState.vHotX; hotY = _mouseCurState.vHotY; } else { scale = 1; + width = _videoMode.overlayWidth; + height = _videoMode.overlayHeight; dst.w = _mouseCurState.rW; dst.h = _mouseCurState.rH; hotX = _mouseCurState.rHotX; diff --git a/backends/platform/gp2xwiz/gp2xwiz-main.cpp b/backends/platform/gp2xwiz/gp2xwiz-main.cpp index 394c3090c3..8c95a94639 100644 --- a/backends/platform/gp2xwiz/gp2xwiz-main.cpp +++ b/backends/platform/gp2xwiz/gp2xwiz-main.cpp @@ -42,7 +42,6 @@ #include "base/main.h" #include "backends/saves/default/default-saves.h" - #include "backends/timer/default/default-timer.h" #include "sound/mixer_intern.h" @@ -64,7 +63,7 @@ int main(int argc, char *argv[]) { #ifdef DYNAMIC_MODULES PluginManager::instance().addPluginProvider(new POSIXPluginProvider()); -#endif +#endif /* DYNAMIC_MODULES */ // Invoke the actual ScummVM main entry point: int res = scummvm_main(argc, argv); diff --git a/backends/platform/gp2xwiz/module.mk b/backends/platform/gp2xwiz/module.mk index edf2f2a717..c76238f679 100644 --- a/backends/platform/gp2xwiz/module.mk +++ b/backends/platform/gp2xwiz/module.mk @@ -4,7 +4,8 @@ MODULE_OBJS := \ gp2xwiz-events.o \ gp2xwiz-graphics.o \ gp2xwiz-hw.o \ - gp2xwiz-main.o + gp2xwiz-main.o \ + gp2xwiz-loader.o # We don't use rules.mk but rather manually update OBJS and MODULE_DIRS. MODULE_OBJS := $(addprefix $(MODULE)/, $(MODULE_OBJS)) diff --git a/backends/platform/linuxmoto/linuxmoto-graphics.cpp b/backends/platform/linuxmoto/linuxmoto-graphics.cpp index a39416ebc4..d66d41dfab 100644 --- a/backends/platform/linuxmoto/linuxmoto-graphics.cpp +++ b/backends/platform/linuxmoto/linuxmoto-graphics.cpp @@ -168,6 +168,7 @@ void OSystem_LINUXMOTO::drawMouse() { SDL_Rect dst; int scale; + int width, height; int hotX, hotY; if (_videoMode.mode == GFX_HALF && !_overlayVisible) { @@ -180,12 +181,16 @@ void OSystem_LINUXMOTO::drawMouse() { if (!_overlayVisible) { scale = _videoMode.scaleFactor; + width = _videoMode.screenWidth; + height = _videoMode.screenHeight; dst.w = _mouseCurState.vW; dst.h = _mouseCurState.vH; hotX = _mouseCurState.vHotX; hotY = _mouseCurState.vHotY; } else { scale = 1; + width = _videoMode.overlayWidth; + height = _videoMode.overlayHeight; dst.w = _mouseCurState.rW; dst.h = _mouseCurState.rH; hotX = _mouseCurState.rHotX; diff --git a/backends/platform/ps2/Makefile.ps2 b/backends/platform/ps2/Makefile.ps2 index d2a8d210e4..f836bd3eaa 100644 --- a/backends/platform/ps2/Makefile.ps2 +++ b/backends/platform/ps2/Makefile.ps2 @@ -1,22 +1,45 @@ # $Header: Exp $ include $(PS2SDK)/Defs.make -PS2_EXTRA = /works/devel/ps2/sdk-extra +PS2_EXTRA = /home/tony/GSOC/ps2/sdk-extra PS2_EXTRA_INCS = /zlib/include /libmad/ee/include /SjPcm/ee/src /tremor PS2_EXTRA_LIBS = /zlib/lib /libmad/ee/lib /SjPcm/ee/lib /tremor/tremor -ENABLED=STATIC_PLUGIN +# Set to 1 to enable, 0 to disable dynamic modules +DYNAMIC_MODULES = 1 +# Set to 1 to enable, 0 to disable more detailed printing of gcc commands +VERBOSE_BUILD=0 + +# Test for dynamic plugins +ifeq ($(DYNAMIC_MODULES),1) +ENABLED = DYNAMIC_PLUGIN +DEFINES = -DDYNAMIC_MODULES +PRE_OBJS_FLAGS = -Wl,--whole-archive +POST_OBJS_FLAGS = -Wl,--no-whole-archive +else +ENABLED = STATIC_PLUGIN +endif + +#control build +DISABLE_SCALERS = true +DISABLE_HQ_SCALERS = true ENABLE_SCUMM = $(ENABLED) ENABLE_SCUMM_7_8 = $(ENABLED) ENABLE_HE = $(ENABLED) ENABLE_AGI = $(ENABLED) ENABLE_AGOS = $(ENABLED) +ENABLE_AGOS2 = $(ENABLED) ENABLE_CINE = $(ENABLED) ENABLE_CRUISE = $(ENABLED) +ENABLE_DRACI = $(ENABLED) ENABLE_DRASCULA = $(ENABLED) ENABLE_GOB = $(ENABLED) +ENABLE_GROOVIE = $(ENABLED) +# ENABLE_GROOVIE2 = $(ENABLED) +ENABLE_IHNM = $(ENABLED) ENABLE_KYRA = $(ENABLED) +# ENABLE_LOL = $(ENABLED) ENABLE_LURE = $(ENABLED) # ENABLE_M4 = $(ENABLED) ENABLE_MADE = $(ENABLED) @@ -24,16 +47,18 @@ ENABLE_PARALLACTION = $(ENABLED) ENABLE_QUEEN = $(ENABLED) ENABLE_SAGA = $(ENABLED) ENABLE_SAGA2 = $(ENABLED) -ENABLE_IHNM = $(ENABLED) +ENABLE_SCI = $(ENABLED) +# ENABLE_SCI32 = $(ENABLED) ENABLE_SKY = $(ENABLED) ENABLE_SWORD1 = $(ENABLED) ENABLE_SWORD2 = $(ENABLED) -# ENABLE_TINSEL = $(ENABLED) +ENABLE_TEENAGENT = $(ENABLED) +ENABLE_TINSEL = $(ENABLED) ENABLE_TOUCHE = $(ENABLED) +ENABLE_TUCKER = $(ENABLED) HAVE_GCC3 = true - -CC = ee-gcc +CC = ee-gcc CXX = ee-g++ AS = ee-gcc LD = ee-gcc @@ -42,51 +67,62 @@ RANLIB = ee-ranlib STRIP = ee-strip MKDIR = mkdir -p RM = rm -f +RM_REC = rm -rf +MODULE_DIRS = ./ srcdir = ../../.. VPATH = $(srcdir) INCDIR = ../../../ -# DEPDIR = .deps +DEPDIR = .deps -DEFINES = -DUSE_VORBIS -DUSE_TREMOR -DUSE_MAD -DUSE_ZLIB -DFORCE_RTL -D_EE -D__PLAYSTATION2__ -O2 -Wall -Wno-multichar +TARGET = elf/scummvm.elf +DEFINES += -DUSE_VORBIS -DUSE_TREMOR -DUSE_MAD -DUSE_ZLIB -DFORCE_RTL -DDISABLE_SAVEGAME_SORTING -D_EE -D__PLAYSTATION2__ -G2 -O2 -Wall -Wno-multichar -fno-rtti -fno-exceptions -DNO_ADAPTOR +DEFINES += -DELF_LOADER_TARGET -DMIPS_TARGET#-DNEW_PLUGIN_DESIGN_FIRST_REFINEMENT INCLUDES = $(addprefix -I$(PS2_EXTRA),$(PS2_EXTRA_INCS)) INCLUDES += -I $(PS2SDK)/ee/include -I $(PS2SDK)/common/include -I ./common -I . -I $(srcdir) -I $(srcdir)/engines -TARGET = elf/scummvm.elf +CXX_UPDATE_DEP_FLAG = -Wp,-MMD,"$(*D)/$(DEPDIR)/$(*F).d",-MQ,"$@",-MP + +# Variables for dynamic plugin building +PLUGIN_PREFIX = +PLUGIN_SUFFIX = .plg +PLUGIN_EXTRA_DEPS = $(srcdir)/backends/plugins/plugin.syms elf/scummvm.elf +PLUGIN_LDFLAGS += -mno-crt0 $(PS2SDK)/ee/startup/crt0.o +PLUGIN_LDFLAGS += -nostartfiles -Wl,-q,--just-symbols,elf/scummvm.elf,-T$(srcdir)/backends/plugins/ps2/plugin.ld,--retain-symbols-file,$(srcdir)/backends/plugins/plugin.syms -lstdc++ -lc -OBJS := backends/platform/ps2/DmaPipe.o \ - backends/platform/ps2/Gs2dScreen.o \ - backends/platform/ps2/irxboot.o \ - backends/platform/ps2/ps2input.o \ - backends/platform/ps2/ps2pad.o \ - backends/platform/ps2/savefilemgr.o \ - backends/platform/ps2/fileio.o \ - backends/platform/ps2/asyncfio.o \ - backends/platform/ps2/icon.o \ - backends/platform/ps2/cd.o \ - backends/platform/ps2/eecodyvdfs.o \ - backends/platform/ps2/rpckbd.o \ - backends/platform/ps2/systemps2.o \ - backends/platform/ps2/ps2mutex.o \ - backends/platform/ps2/ps2time.o \ - backends/platform/ps2/ps2debug.o - -MODULE_DIRS += . BACKEND := ps2 include $(srcdir)/Makefile.common -LDFLAGS += -mno-crt0 $(PS2SDK)/ee/startup/crt0.o -T $(PS2SDK)/ee/startup/linkfile +LDFLAGS = -mno-crt0 $(PS2SDK)/ee/startup/crt0.o -T $(srcdir)/backends/plugins/ps2/main_prog.ld LDFLAGS += -L $(PS2SDK)/ee/lib -L . -LDFLAGS += $(addprefix -L$(PS2_EXTRA),$(PS2_EXTRA_LIBS)) +LDFLAGS += $(addprefix -L$(PS2_EXTRA),$(PS2_EXTRA_LIBS)) LDFLAGS += -lmc -lpad -lmouse -lhdd -lpoweroff -lsjpcm -lmad -ltremor -lz -lm -lc -lfileXio -lkernel -lstdc++ -LDFLAGS += -s + +OBJS := $(srcdir)/backends/platform/ps2/DmaPipe.o \ + $(srcdir)/backends/platform/ps2/Gs2dScreen.o \ + $(srcdir)/backends/platform/ps2/irxboot.o \ + $(srcdir)/backends/platform/ps2/ps2input.o \ + $(srcdir)/backends/platform/ps2/ps2pad.o \ + $(srcdir)/backends/platform/ps2/savefilemgr.o \ + $(srcdir)/backends/platform/ps2/fileio.o \ + $(srcdir)/backends/platform/ps2/asyncfio.o \ + $(srcdir)/backends/platform/ps2/icon.o \ + $(srcdir)/backends/platform/ps2/cd.o \ + $(srcdir)/backends/platform/ps2/eecodyvdfs.o \ + $(srcdir)/backends/platform/ps2/rpckbd.o \ + $(srcdir)/backends/platform/ps2/systemps2.o \ + $(srcdir)/backends/platform/ps2/ps2mutex.o \ + $(srcdir)/backends/platform/ps2/ps2time.o \ + $(srcdir)/backends/platform/ps2/ps2debug.o + +include $(srcdir)/Makefile.common all: $(TARGET) $(TARGET): $(OBJS) - $(LD) $^ $(LDFLAGS) -o $@ + $(LD) $(PRE_OBJS_FLAGS) $(OBJS) $(POST_OBJS_FLAGS) $(LDFLAGS) -o $@ diff --git a/backends/platform/ps2/elf32.h b/backends/platform/ps2/elf32.h new file mode 100644 index 0000000000..616cc4b4d2 --- /dev/null +++ b/backends/platform/ps2/elf32.h @@ -0,0 +1,209 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef BACKENDS_ELF_H +#define BACKENDS_ELF_H + +/* ELF stuff */ + +typedef unsigned short Elf32_Half, Elf32_Section; +typedef unsigned int Elf32_Word, Elf32_Addr, Elf32_Off; +typedef signed int Elf32_Sword; +typedef Elf32_Half Elf32_Versym; + +#define EI_NIDENT (16) +#define SELFMAG 6 + +/* ELF File format structures. Look up ELF structure for more details */ + +// ELF header (contains info about the file) +typedef struct { + unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ + Elf32_Half e_type; /* Object file type */ + Elf32_Half e_machine; /* Architecture */ + Elf32_Word e_version; /* Object file version */ + Elf32_Addr e_entry; /* Entry point virtual address */ + Elf32_Off e_phoff; /* Program header table file offset */ + Elf32_Off e_shoff; /* Section header table file offset */ + Elf32_Word e_flags; /* Processor-specific flags */ + Elf32_Half e_ehsize; /* ELF header size in bytes */ + Elf32_Half e_phentsize; /* Program header table entry size */ + Elf32_Half e_phnum; /* Program header table entry count */ + Elf32_Half e_shentsize; /* Section header table entry size */ + Elf32_Half e_shnum; /* Section header table entry count */ + Elf32_Half e_shstrndx; /* Section header string table index */ +} Elf32_Ehdr; + +// Should be in e_ident +#define ELFMAG "\177ELF\1\1" /* ELF Magic number */ + +// e_type values +#define ET_NONE 0 /* no file type */ +#define ET_REL 1 /* relocatable */ +#define ET_EXEC 2 /* executable */ +#define ET_DYN 3 /* shared object */ +#define ET_CORE 4 /* core file */ + +// e_machine values +#define EM_MIPS 8 + + +// Program header (contains info about segment) +typedef struct { + Elf32_Word p_type; /* Segment type */ + Elf32_Off p_offset; /* Segment file offset */ + Elf32_Addr p_vaddr; /* Segment virtual address */ + Elf32_Addr p_paddr; /* Segment physical address */ + Elf32_Word p_filesz; /* Segment size in file */ + Elf32_Word p_memsz; /* Segment size in memory */ + Elf32_Word p_flags; /* Segment flags */ + Elf32_Word p_align; /* Segment alignment */ +} Elf32_Phdr; + +// p_type values +#define PT_NULL 0 /* ignored */ +#define PT_LOAD 1 /* loadable segment */ +#define PT_DYNAMIC 2 /* dynamic linking info */ +#define PT_INTERP 3 /* info about interpreter */ +#define PT_NOTE 4 /* note segment */ +#define PT_SHLIB 5 /* reserved */ +#define PT_PHDR 6 /* Program header table */ +#define PT_MIPS_REGINFO 0x70000000 /* register usage info */ + +// p_flags value +#define PF_X 1 /* execute */ +#define PF_W 2 /* write */ +#define PF_R 4 /* read */ + +// Section header (contains info about section) +typedef struct { + Elf32_Word sh_name; /* Section name (string tbl index) */ + Elf32_Word sh_type; /* Section type */ + Elf32_Word sh_flags; /* Section flags */ + Elf32_Addr sh_addr; /* Section virtual addr at execution */ + Elf32_Off sh_offset; /* Section file offset */ + Elf32_Word sh_size; /* Section size in bytes */ + Elf32_Word sh_link; /* Link to another section */ + Elf32_Word sh_info; /* Additional section information */ + Elf32_Word sh_addralign; /* Section alignment */ + Elf32_Word sh_entsize; /* Entry size if section holds table */ +} Elf32_Shdr; + +// sh_type values +#define SHT_NULL 0 /* Inactive section */ +#define SHT_PROGBITS 1 /* Proprietary */ +#define SHT_SYMTAB 2 /* Symbol table */ +#define SHT_STRTAB 3 /* String table */ +#define SHT_RELA 4 /* Relocation entries with addend */ +#define SHT_HASH 5 /* Symbol hash table */ +#define SHT_DYNAMIC 6 /* Info for dynamic linking */ +#define SHT_NOTE 7 /* Note section */ +#define SHT_NOBITS 8 /* Occupies no space */ +#define SHT_REL 9 /* Relocation entries without addend */ +#define SHT_SHLIB 10 /* Reserved */ +#define SHT_DYNSYM 11 /* Minimal set of dynamic linking symbols */ +#define SHT_MIPS_LIBLSIT 0x70000000 /* Info about dynamic shared object libs */ +#define SHT_MIPS_CONFLICT 0x70000002 /* Conflicts btw executables and shared objects */ +#define SHT_MIPS_GPTAB 0x70000003 /* Global pointer table */ + +// sh_flags values +#define SHF_WRITE 0 /* writable section */ +#define SHF_ALLOC 2 /* section occupies memory */ +#define SHF_EXECINSTR 4 /* machine instructions */ +#define SHF_MIPS_GPREL 0x10000000 /* Must be made part of global data area */ + + +// Symbol entry (contain info about a symbol) +typedef struct { + Elf32_Word st_name; /* Symbol name (string tbl index) */ + Elf32_Addr st_value; /* Symbol value */ + Elf32_Word st_size; /* Symbol size */ + unsigned char st_info; /* Symbol type and binding */ + unsigned char st_other; /* Symbol visibility */ + Elf32_Section st_shndx; /* Section index */ +} Elf32_Sym; + +// Extract from the st_info +#define SYM_TYPE(x) ((x)&0xF) +#define SYM_BIND(x) ((x)>>4) + + +// Symbol binding values from st_info +#define STB_LOCAL 0 /* Symbol not visible outside object */ +#define STB_GLOBAL 1 /* Symbol visible to all object files */ +#define STB_WEAK 2 /* Similar to STB_GLOBAL */ + +// Symbol type values from st_info +#define STT_NOTYPE 0 /* Not specified */ +#define STT_OBJECT 1 /* Data object e.g. variable */ +#define STT_FUNC 2 /* Function */ +#define STT_SECTION 3 /* Section */ +#define STT_FILE 4 /* Source file associated with object file */ + +// Special section header index values from st_shndex +#define SHN_UNDEF 0 +#define SHN_LOPROC 0xFF00 /* Extended values */ +#define SHN_ABS 0xFFF1 /* Absolute value: don't relocate */ +#define SHN_COMMON 0xFFF2 /* Common block. Not allocated yet */ +#define SHN_HIPROC 0xFF1F +#define SHN_HIRESERVE 0xFFFF + +// Relocation entry (info about how to relocate) +typedef struct { + Elf32_Addr r_offset; /* Address */ + Elf32_Word r_info; /* Relocation type and symbol index */ +} Elf32_Rel; + +// Access macros for the relocation info +#define REL_TYPE(x) ((x)&0xFF) /* Extract relocation type */ +#define REL_INDEX(x) ((x)>>8) /* Extract relocation index into symbol table */ + +// MIPS relocation types +#define R_MIPS_NONE 0 +#define R_MIPS_16 1 +#define R_MIPS_32 2 +#define R_MIPS_REL32 3 +#define R_MIPS_26 4 +#define R_MIPS_HI16 5 +#define R_MIPS_LO16 6 +#define R_MIPS_GPREL16 7 +#define R_MIPS_LITERAL 8 +#define R_MIPS_GOT16 9 +#define R_MIPS_PC16 10 +#define R_MIPS_CALL16 11 +#define R_MIPS_GPREL32 12 +#define R_MIPS_GOTHI16 13 +#define R_MIPS_GOTLO16 14 +#define R_MIPS_CALLHI16 15 +#define R_MIPS_CALLLO16 16 + +// Mock function to get value of global pointer +#define getGP() ({ \ + unsigned int __valgp; \ + __asm__ ("add %0, $gp, $0" : "=r"(__valgp) : ); \ + __valgp; \ +}) + +#endif /* BACKENDS_ELF_H */ diff --git a/backends/platform/ps2/module.mk b/backends/platform/ps2/module.mk index bf95a5501d..a12d10771f 100644 --- a/backends/platform/ps2/module.mk +++ b/backends/platform/ps2/module.mk @@ -16,7 +16,8 @@ MODULE_OBJS := \ systemps2.o \ ps2mutex.o \ ps2time.o \ - ps2debug.o + ps2debug.o \ + ps2loader.o # We don't use rules.mk but rather manually update OBJS and MODULE_DIRS. MODULE_OBJS := $(addprefix $(MODULE)/, $(MODULE_OBJS)) diff --git a/backends/platform/ps2/systemps2.cpp b/backends/platform/ps2/systemps2.cpp index 7659d5194d..357404c5c4 100644 --- a/backends/platform/ps2/systemps2.cpp +++ b/backends/platform/ps2/systemps2.cpp @@ -59,6 +59,8 @@ #include "backends/platform/ps2/ps2debug.h" #include "backends/fs/ps2/ps2-fs-factory.h" +#include "backends/plugins/ps2/ps2-provider.h" + #include "backends/saves/default/default-saves.h" #include "common/config-manager.h" @@ -105,7 +107,6 @@ extern "C" int scummvm_main(int argc, char *argv[]); extern "C" int main(int argc, char *argv[]) { SifInitRpc(0); - ee_thread_t thisThread; int tid = GetThreadId(); ReferThreadStatus(tid, &thisThread); @@ -130,8 +131,11 @@ extern "C" int main(int argc, char *argv[]) { sioprintf("Creating system\n"); g_system = g_systemPs2 = new OSystem_PS2(argv[0]); - g_systemPs2->init(); +#ifdef DYNAMIC_MODULES + PluginManager::instance().addPluginProvider(new PS2PluginProvider()); +#endif + g_systemPs2->init(); sioprintf("init done. starting ScummVM.\n"); int res = scummvm_main(argc, argv); sioprintf("scummvm_main terminated: %d\n", res); @@ -337,6 +341,7 @@ OSystem_PS2::OSystem_PS2(const char *elfPath) { } void OSystem_PS2::init(void) { + //msgPrintf(FOREVER, "got to init! Now restart your console... %d\n", res); sioprintf("Timer...\n"); _scummTimerManager = new DefaultTimerManager(); _scummMixer = new Audio::MixerImpl(this, 48000); diff --git a/backends/platform/psp/Makefile b/backends/platform/psp/Makefile index 617ef7c8cc..9c011220b8 100644 --- a/backends/platform/psp/Makefile +++ b/backends/platform/psp/Makefile @@ -67,7 +67,7 @@ endif # Variables for common Scummvm makefile CXX = psp-g++ CXXFLAGS = -O3 -Wall -Wno-multichar -fno-exceptions -fno-rtti -DEFINES = -D__PSP__ -DNONSTANDARD_PORT -DDISABLE_TEXT_CONSOLE -DDISABLE_COMMAND_LINE -DUSE_ZLIB -DDISABLE_DOSBOX_OPL -DUSE_RGB_COLOR +DEFINES = -D__PSP__ -DNONSTANDARD_PORT -DDISABLE_TEXT_CONSOLE -DDISABLE_COMMAND_LINE -DUSE_ZLIB -DDISABLE_DOSBOX_OPL -DUSE_RGB_COLOR -DELF_LOADER_TARGET -DMIPS_TARGET LDFLAGS := INCDIR := $(srcdir) . $(srcdir)/engines/ $(PSPSDK)/include @@ -87,8 +87,8 @@ CXX_UPDATE_DEP_FLAG = -Wp,-MMD,"$(*D)/$(DEPDIR)/$(*F).d",-MQ,"$@",-MP # Variables for dynamic plugin building PLUGIN_PREFIX = PLUGIN_SUFFIX = .plg -PLUGIN_EXTRA_DEPS = plugin.syms scummvm-psp.elf -PLUGIN_LDFLAGS = -nostartfiles -Wl,-q,--just-symbols=scummvm-psp.org.elf,-Tplugin.ld,--retain-symbols-file,plugin.syms -lstdc++ -lc +PLUGIN_EXTRA_DEPS = $(srcdir)/backends/plugins/plugin.syms scummvm-psp.elf +PLUGIN_LDFLAGS = -nostartfiles -Wl,-q,--just-symbols=scummvm-psp.org.elf,-T$(srcdir)/backends/plugins/psp/plugin.ld,--retain-symbols-file,$(srcdir)/backends/plugins/plugin.syms -lstdc++ -lc # PSP-specific variables STRIP = psp-strip @@ -125,8 +125,7 @@ endif # PSP LIBS PSPLIBS = -lpspprof -lpspvfpu -lpspdebug -lpspgu -lpspge -lpspdisplay -lpspctrl -lpspsdk \ - -lpsputility -lpspuser -lpsppower -lpsphprm -lpspsdk -lpsprtc -lpspaudio -lpspaudiocodec \ - -lpspkernel + -lpsputility -lpspuser -lpsppower -lpsphprm -lpspsdk -lpsprtc -lpspaudio -lpspkernel # Add in PSPSDK includes and libraries. LIBS += -lpng -lz -lstdc++ -lc -lm $(PSPLIBS) @@ -150,6 +149,7 @@ OBJS := powerman.o \ mp3.o \ tests.o + BACKEND := psp # Include common Scummvm makefile diff --git a/backends/platform/psp/audio.cpp b/backends/platform/psp/audio.cpp index e540733162..14691befee 100644 --- a/backends/platform/psp/audio.cpp +++ b/backends/platform/psp/audio.cpp @@ -18,8 +18,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/backends/platform/psp/osys_psp.cpp $ - * $Id: osys_psp.cpp 46126 2009-11-24 14:18:46Z fingolfin $ + * $URL$ + * $Id$ * */ diff --git a/backends/platform/psp/audio.h b/backends/platform/psp/audio.h index eeba598fed..07f70cec7d 100644 --- a/backends/platform/psp/audio.h +++ b/backends/platform/psp/audio.h @@ -18,8 +18,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/backends/platform/psp/osys_psp.cpp $ - * $Id: osys_psp.cpp 46126 2009-11-24 14:18:46Z fingolfin $ + * $URL$ + * $Id$ * */ diff --git a/backends/platform/psp/cursor.cpp b/backends/platform/psp/cursor.cpp index ae3b8f0050..cf879e095a 100644 --- a/backends/platform/psp/cursor.cpp +++ b/backends/platform/psp/cursor.cpp @@ -18,8 +18,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/backends/platform/psp/osys_psp.h $ - * $Id: osys_psp.h 46120 2009-11-24 10:33:30Z Bluddy $ + * $URL$ + * $Id$ * */ diff --git a/backends/platform/psp/default_display_client.h b/backends/platform/psp/default_display_client.h index 716e6fcc35..2e33632eb1 100644 --- a/backends/platform/psp/default_display_client.h +++ b/backends/platform/psp/default_display_client.h @@ -18,8 +18,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/backends/platform/psp/trace.h $ - * $Id: trace.h 44276 2009-09-23 16:11:23Z joostp $ + * $URL$ + * $Id$ * */ diff --git a/backends/platform/psp/display_client.cpp b/backends/platform/psp/display_client.cpp index 71b505ec7c..6360772c96 100644 --- a/backends/platform/psp/display_client.cpp +++ b/backends/platform/psp/display_client.cpp @@ -18,8 +18,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/backends/platform/psp/osys_psp.cpp $ - * $Id: osys_psp.cpp 46126 2009-11-24 14:18:46Z fingolfin $ + * $URL$ + * $Id$ * */ diff --git a/backends/platform/psp/display_manager.cpp b/backends/platform/psp/display_manager.cpp index 5037543f12..544e5a1b25 100644 --- a/backends/platform/psp/display_manager.cpp +++ b/backends/platform/psp/display_manager.cpp @@ -18,8 +18,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/backends/platform/psp/osys_psp.cpp $ - * $Id: osys_psp.cpp 47541 2010-01-25 01:39:44Z lordhoto $ + * $URL$ + * $Id$ * */ diff --git a/backends/platform/psp/display_manager.h b/backends/platform/psp/display_manager.h index 1f7320902c..72f252faae 100644 --- a/backends/platform/psp/display_manager.h +++ b/backends/platform/psp/display_manager.h @@ -18,8 +18,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/backends/platform/psp/osys_psp.cpp $ - * $Id: osys_psp.cpp 47541 2010-01-25 01:39:44Z lordhoto $ + * $URL$ + * $Id$ * */ diff --git a/backends/platform/psp/input.cpp b/backends/platform/psp/input.cpp index 4fe7cb3f92..2a91ce455a 100644 --- a/backends/platform/psp/input.cpp +++ b/backends/platform/psp/input.cpp @@ -18,8 +18,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/backends/platform/psp/osys_psp.cpp $ - * $Id: osys_psp.cpp 43618 2009-08-21 22:44:49Z joostp $ + * $URL$ + * $Id$ * */ diff --git a/backends/platform/psp/psploader.cpp b/backends/platform/psp/psploader.cpp deleted file mode 100644 index 464e20770c..0000000000 --- a/backends/platform/psp/psploader.cpp +++ /dev/null @@ -1,732 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#if defined(DYNAMIC_MODULES) && defined(__PSP__) - -#include <string.h> -#include <stdarg.h> -#include <stdio.h> -#include <malloc.h> -#include <unistd.h> -#include <sys/_default_fcntl.h> - -#include <psputils.h> - -#include "backends/platform/psp/psploader.h" -#include "backends/platform/psp/powerman.h" - -//#define __PSP_DEBUG_FUNCS__ /* For debugging the stack */ -//#define __PSP_DEBUG_PRINT__ - -#include "backends/platform/psp/trace.h" - -extern char __plugin_hole_start; // Indicates start of hole in program file for shorts -extern char __plugin_hole_end; // Indicates end of hole in program file -extern char _gp[]; // Value of gp register - -DECLARE_SINGLETON(ShortSegmentManager) // For singleton - -// Get rid of symbol table in memory -void DLObject::discard_symtab() { - DEBUG_ENTER_FUNC(); - free(_symtab); - free(_strtab); - _symtab = NULL; - _strtab = NULL; - _symbol_cnt = 0; -} - -// Unload all objects from memory -void DLObject::unload() { - DEBUG_ENTER_FUNC(); - discard_symtab(); - free(_segment); - _segment = NULL; - - if (_shortsSegment) { - ShortsMan.deleteSegment(_shortsSegment); - _shortsSegment = NULL; - } -} - -/** - * Follow the instruction of a relocation section. - * - * @param fd File Descriptor - * @param offset Offset into the File - * @param size Size of relocation section - * @param relSegment Base address of relocated segment in memory (memory offset) - * - */ -bool DLObject::relocate(int fd, unsigned long offset, unsigned long size, void *relSegment) { - DEBUG_ENTER_FUNC(); - Elf32_Rel *rel = NULL; // relocation entry - - // Allocate memory for relocation table - if (!(rel = (Elf32_Rel *)malloc(size))) { - PSP_ERROR("Out of memory."); - return false; - } - - // Read in our relocation table - if (lseek(fd, offset, SEEK_SET) < 0 || - read(fd, rel, size) != (ssize_t)size) { - PSP_ERROR("Relocation table load failed."); - free(rel); - return false; - } - - // Treat each relocation entry. Loop over all of them - int cnt = size / sizeof(*rel); - - PSP_DEBUG_PRINT("Loaded relocation table. %d entries. base address=%p\n", cnt, relSegment); - - bool seenHi16 = false; // For treating HI/LO16 commands - int firstHi16 = -1; // Mark the point of the first hi16 seen - Elf32_Addr ahl = 0; // Calculated addend - int a = 0; // Addend: taken from the target - - unsigned int *lastTarget = 0; // For processing hi16 when lo16 arrives - unsigned int relocation = 0; - int debugRelocs[10] = {0}; // For debugging - int extendedHi16 = 0; // Count extended hi16 treatments - Elf32_Addr lastHiSymVal = 0; - bool hi16InShorts = false; - -#define DEBUG_NUM 2 - - // Loop over relocation entries - for (int i = 0; i < cnt; i++) { - // Get the symbol this relocation entry is referring to - Elf32_Sym *sym = (Elf32_Sym *)(_symtab) + (REL_INDEX(rel[i].r_info)); - - // Get the target instruction in the code - unsigned int *target = (unsigned int *)((char *)relSegment + rel[i].r_offset); - - PSP_DEBUG_DO(unsigned int origTarget = *target); // Save for debugging - - // Act differently based on the type of relocation - switch (REL_TYPE(rel[i].r_info)) { - - case R_MIPS_HI16: // Absolute addressing. - if (sym->st_shndx < SHN_LOPROC && // Only shift for plugin section (ie. has a real section index) - firstHi16 < 0) { // Only process first in block of HI16s - firstHi16 = i; // Keep the first Hi16 we saw - seenHi16 = true; - ahl = (*target & 0xffff) << 16; // Take lower 16 bits shifted up - - lastHiSymVal = sym->st_value; - hi16InShorts = (ShortsMan.inGeneralSegment((char *)sym->st_value)); // Fix for problem with switching btw segments - if (debugRelocs[0]++ < DEBUG_NUM) // Print only a set number - PSP_DEBUG_PRINT("R_MIPS_HI16: i=%d, offset=%x, ahl = %x, target = %x\n", - i, rel[i].r_offset, ahl, *target); - } - break; - - case R_MIPS_LO16: // Absolute addressing. Needs a HI16 to come before it - if (sym->st_shndx < SHN_LOPROC) { // Only shift for plugin section. (ie. has a real section index) - if (!seenHi16) { // We MUST have seen HI16 first - PSP_ERROR("R_MIPS_LO16 w/o preceding R_MIPS_HI16 at relocation %d!\n", i); - free(rel); - return false; - } - - // Fix: bug in gcc makes LO16s connect to wrong HI16s sometimes (shorts and regular segment) - // Note that we can check the entire shorts segment because the executable's shorts don't belong to this plugin section - // and will be screened out above - bool lo16InShorts = ShortsMan.inGeneralSegment((char *)sym->st_value); - - // Correct the bug by getting the proper value in ahl (taken from the current symbol) - if ((hi16InShorts && !lo16InShorts) || (!hi16InShorts && lo16InShorts)) { - ahl -= (lastHiSymVal & 0xffff0000); // We assume gcc meant the same offset - ahl += (sym->st_value & 0xffff0000); - } - - ahl &= 0xffff0000; // Clean lower 16 bits for repeated LO16s - a = *target & 0xffff; // Take lower 16 bits of the target - a = (a << 16) >> 16; // Sign extend them - ahl += a; // Add lower 16 bits. AHL is now complete - - // Fix: we can have LO16 access to the short segment sometimes - if (lo16InShorts) { - relocation = ahl + _shortsSegment->getOffset(); // Add in the short segment offset - } else // It's in the regular segment - relocation = ahl + (Elf32_Addr)_segment; // Add in the new offset for the segment - - if (firstHi16 >= 0) { // We haven't treated the HI16s yet so do it now - for (int j = firstHi16; j < i; j++) { - if (REL_TYPE(rel[j].r_info) != R_MIPS_HI16) continue; // Skip over non-Hi16s - - lastTarget = (unsigned int *)((char *)relSegment + rel[j].r_offset); // get hi16 target - *lastTarget &= 0xffff0000; // Clear the lower 16 bits of the last target - *lastTarget |= (relocation >> 16) & 0xffff; // Take the upper 16 bits of the relocation - if (relocation & 0x8000)(*lastTarget)++; // Subtle: we need to add 1 to the HI16 in this case - } - firstHi16 = -1; // Reset so we'll know we treated it - } else { - extendedHi16++; - } - - *target &= 0xffff0000; // Clear the lower 16 bits of current target - *target |= relocation & 0xffff; // Take the lower 16 bits of the relocation - - if (debugRelocs[1]++ < DEBUG_NUM) - PSP_DEBUG_PRINT("R_MIPS_LO16: i=%d, offset=%x, a=%x, ahl = %x, lastTarget = %x, origt = %x, target = %x\n", - i, rel[i].r_offset, a, ahl, *lastTarget, origTarget, *target); - if (lo16InShorts && debugRelocs[2]++ < DEBUG_NUM) - PSP_DEBUG_PRINT("R_MIPS_LO16s: i=%d, offset=%x, a=%x, ahl = %x, lastTarget = %x, origt = %x, target = %x\n", - i, rel[i].r_offset, a, ahl, *lastTarget, origTarget, *target); - } - break; - - case R_MIPS_26: // Absolute addressing (for jumps and branches only) - if (sym->st_shndx < SHN_LOPROC) { // Only relocate for main segment - a = *target & 0x03ffffff; // Get 26 bits' worth of the addend - a = (a << 6) >> 6; // Sign extend a - relocation = ((a << 2) + (Elf32_Addr)_segment) >> 2; // a already points to the target. Subtract our offset - *target &= 0xfc000000; // Clean lower 26 target bits - *target |= (relocation & 0x03ffffff); - - if (debugRelocs[3]++ < DEBUG_NUM) - PSP_DEBUG_PRINT("R_MIPS_26: i=%d, offset=%x, symbol=%d, stinfo=%x, a=%x, origTarget=%x, target=%x\n", - i, rel[i].r_offset, REL_INDEX(rel[i].r_info), sym->st_info, a, origTarget, *target); - } else { - if (debugRelocs[4]++ < DEBUG_NUM) - PSP_DEBUG_PRINT("R_MIPS_26: i=%d, offset=%x, symbol=%d, stinfo=%x, a=%x, origTarget=%x, target=%x\n", - i, rel[i].r_offset, REL_INDEX(rel[i].r_info), sym->st_info, a, origTarget, *target); - } - break; - - case R_MIPS_GPREL16: // GP Relative addressing - if (_shortsSegment->getOffset() != 0 && // Only relocate if we shift the shorts section - ShortsMan.inGeneralSegment((char *)sym->st_value)) { // Only relocate things in the plugin hole - a = *target & 0xffff; // Get 16 bits' worth of the addend - a = (a << 16) >> 16; // Sign extend it - - relocation = a + _shortsSegment->getOffset(); - - *target &= 0xffff0000; // Clear the lower 16 bits of the target - *target |= relocation & 0xffff; - - if (debugRelocs[5]++ < DEBUG_NUM) - PSP_DEBUG_PRINT("R_MIPS_GPREL16: i=%d, a=%x, gpVal=%x, origTarget=%x, target=%x, offset=%x\n", - i, a, _gpVal, origTarget, *target, _shortsSegment->getOffset()); - } - - break; - - case R_MIPS_32: // Absolute addressing - if (sym->st_shndx < SHN_LOPROC) { // Only shift for plugin section. - a = *target; // Get full 32 bits of addend - - if (ShortsMan.inGeneralSegment((char *)sym->st_value)) // Check if we're in the shorts segment - relocation = a + _shortsSegment->getOffset(); // Shift by shorts offset - else // We're in the main section - relocation = a + (Elf32_Addr)_segment; // Shift by main offset - *target = relocation; - - if (debugRelocs[6]++ < DEBUG_NUM) - PSP_DEBUG_PRINT("R_MIPS_32: i=%d, a=%x, origTarget=%x, target=%x\n", i, a, origTarget, *target); - } - break; - - default: - PSP_ERROR("Unknown relocation type %x at relocation %d.\n", REL_TYPE(rel[i].r_info), i); - free(rel); - return false; - } - } - - PSP_DEBUG_PRINT("Done with relocation. extendedHi16=%d\n\n", extendedHi16); - - free(rel); - return true; -} - -bool DLObject::readElfHeader(int fd, Elf32_Ehdr *ehdr) { - DEBUG_ENTER_FUNC(); - // Start reading the elf header. Check for errors - if (read(fd, ehdr, sizeof(*ehdr)) != sizeof(*ehdr) || - memcmp(ehdr->e_ident, ELFMAG, SELFMAG) || // Check MAGIC - ehdr->e_type != ET_EXEC || // Check for executable - ehdr->e_machine != EM_MIPS || // Check for MIPS machine type - ehdr->e_phentsize < sizeof(Elf32_Phdr) || // Check for size of program header - ehdr->e_shentsize != sizeof(Elf32_Shdr)) { // Check for size of section header - PSP_ERROR("Invalid file type."); - return false; - } - - PSP_DEBUG_PRINT("phoff = %d, phentsz = %d, phnum = %d\n", - ehdr->e_phoff, ehdr->e_phentsize, ehdr->e_phnum); - - return true; -} - -bool DLObject::readProgramHeaders(int fd, Elf32_Ehdr *ehdr, Elf32_Phdr *phdr, int num) { - DEBUG_ENTER_FUNC(); - // Read program header - if (lseek(fd, ehdr->e_phoff + sizeof(*phdr)*num, SEEK_SET) < 0 || - read(fd, phdr, sizeof(*phdr)) != sizeof(*phdr)) { - PSP_ERROR("Program header load failed."); - return false; - } - - // Check program header values - if (phdr->p_type != PT_LOAD || phdr->p_filesz > phdr->p_memsz) { - PSP_ERROR("Invalid program header."); - return false; - } - - PSP_DEBUG_PRINT("offs = %x, filesz = %x, memsz = %x, align = %x\n", - phdr->p_offset, phdr->p_filesz, phdr->p_memsz, phdr->p_align); - - return true; - -} - -bool DLObject::loadSegment(int fd, Elf32_Phdr *phdr) { - DEBUG_ENTER_FUNC(); - - char *baseAddress = 0; - - // We need to take account of non-allocated segment for shorts - if (phdr->p_flags & PF_X) { // This is a relocated segment - - // Attempt to allocate memory for segment - int extra = phdr->p_vaddr % phdr->p_align; // Get extra length TODO: check logic here - PSP_DEBUG_PRINT("extra mem is %x\n", extra); - - if (phdr->p_align < 0x10000) phdr->p_align = 0x10000; // Fix for wrong alignment on e.g. AGI - - if (!(_segment = (char *)memalign(phdr->p_align, phdr->p_memsz + extra))) { - PSP_ERROR("Out of memory.\n"); - return false; - } - PSP_DEBUG_PRINT("allocated segment @ %p\n", _segment); - - // Get offset to load segment into - baseAddress = (char *)_segment + phdr->p_vaddr; - _segmentSize = phdr->p_memsz + extra; - } else { // This is a shorts section. - _shortsSegment = ShortsMan.newSegment(phdr->p_memsz, (char *)phdr->p_vaddr); - - baseAddress = _shortsSegment->getStart(); - PSP_DEBUG_PRINT("shorts segment @ %p to %p. Segment wants to be at %x. Offset=%x\n", - _shortsSegment->getStart(), _shortsSegment->getEnd(), phdr->p_vaddr, _shortsSegment->getOffset()); - - } - - // Set bss segment to 0 if necessary (assumes bss is at the end) - if (phdr->p_memsz > phdr->p_filesz) { - PSP_DEBUG_PRINT("Setting %p to %p to 0 for bss\n", baseAddress + phdr->p_filesz, baseAddress + phdr->p_memsz); - memset(baseAddress + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz); - } - // Read the segment into memory - if (lseek(fd, phdr->p_offset, SEEK_SET) < 0 || - read(fd, baseAddress, phdr->p_filesz) != (ssize_t)phdr->p_filesz) { - PSP_ERROR("Segment load failed."); - return false; - } - - return true; -} - - -Elf32_Shdr * DLObject::loadSectionHeaders(int fd, Elf32_Ehdr *ehdr) { - DEBUG_ENTER_FUNC(); - - Elf32_Shdr *shdr = NULL; - - // Allocate memory for section headers - if (!(shdr = (Elf32_Shdr *)malloc(ehdr->e_shnum * sizeof(*shdr)))) { - PSP_ERROR("Out of memory."); - return NULL; - } - - // Read from file into section headers - if (lseek(fd, ehdr->e_shoff, SEEK_SET) < 0 || - read(fd, shdr, ehdr->e_shnum * sizeof(*shdr)) != - (ssize_t)(ehdr->e_shnum * sizeof(*shdr))) { - PSP_ERROR("Section headers load failed."); - return NULL; - } - - return shdr; -} - -int DLObject::loadSymbolTable(int fd, Elf32_Ehdr *ehdr, Elf32_Shdr *shdr) { - DEBUG_ENTER_FUNC(); - - // Loop over sections, looking for symbol table linked to a string table - for (int i = 0; i < ehdr->e_shnum; i++) { - PSP_DEBUG_PRINT("Section %d: type = %x, size = %x, entsize = %x, link = %x\n", - i, shdr[i].sh_type, shdr[i].sh_size, shdr[i].sh_entsize, shdr[i].sh_link); - - if (shdr[i].sh_type == SHT_SYMTAB && - shdr[i].sh_entsize == sizeof(Elf32_Sym) && - shdr[i].sh_link < ehdr->e_shnum && - shdr[shdr[i].sh_link].sh_type == SHT_STRTAB && - _symtab_sect < 0) { - _symtab_sect = i; - } - } - - // Check for no symbol table - if (_symtab_sect < 0) { - PSP_ERROR("No symbol table."); - return -1; - } - - PSP_DEBUG_PRINT("Symbol section at section %d, size %x\n", _symtab_sect, shdr[_symtab_sect].sh_size); - - // Allocate memory for symbol table - if (!(_symtab = malloc(shdr[_symtab_sect].sh_size))) { - PSP_ERROR("Out of memory."); - return -1; - } - - // Read symbol table into memory - if (lseek(fd, shdr[_symtab_sect].sh_offset, SEEK_SET) < 0 || - read(fd, _symtab, shdr[_symtab_sect].sh_size) != - (ssize_t)shdr[_symtab_sect].sh_size) { - PSP_ERROR("Symbol table load failed."); - return -1; - } - - // Set number of symbols - _symbol_cnt = shdr[_symtab_sect].sh_size / sizeof(Elf32_Sym); - PSP_DEBUG_PRINT("Loaded %d symbols.\n", _symbol_cnt); - - return _symtab_sect; - -} - -bool DLObject::loadStringTable(int fd, Elf32_Shdr *shdr) { - DEBUG_ENTER_FUNC(); - - int string_sect = shdr[_symtab_sect].sh_link; - - // Allocate memory for string table - if (!(_strtab = (char *)malloc(shdr[string_sect].sh_size))) { - PSP_ERROR("Out of memory."); - return false; - } - - // Read string table into memory - if (lseek(fd, shdr[string_sect].sh_offset, SEEK_SET) < 0 || - read(fd, _strtab, shdr[string_sect].sh_size) != - (ssize_t)shdr[string_sect].sh_size) { - PSP_ERROR("Symbol table strings load failed."); - return false; - } - return true; -} - -void DLObject::relocateSymbols(Elf32_Addr offset, Elf32_Addr shortsOffset) { - DEBUG_ENTER_FUNC(); - - int shortsCount = 0, othersCount = 0; - PSP_DEBUG_PRINT("Relocating symbols by %x. Shorts offset=%x\n", offset, shortsOffset); - - // Loop over symbols, add relocation offset - Elf32_Sym *s = (Elf32_Sym *)_symtab; - for (int c = _symbol_cnt; c--; s++) { - // Make sure we don't relocate special valued symbols - if (s->st_shndx < SHN_LOPROC) { - if (!ShortsMan.inGeneralSegment((char *)s->st_value)) { - othersCount++; - s->st_value += offset; - if (s->st_value < (Elf32_Addr)_segment || s->st_value > (Elf32_Addr)_segment + _segmentSize) - PSP_ERROR("Symbol out of bounds! st_value = %x\n", s->st_value); - } else { // shorts section - shortsCount++; - s->st_value += shortsOffset; - if (!_shortsSegment->inSegment((char *)s->st_value)) - PSP_ERROR("Symbol out of bounds! st_value = %x\n", s->st_value); - } - - } - - } - - PSP_DEBUG_PRINT("Relocated %d short symbols, %d others.\n", shortsCount, othersCount); -} - -bool DLObject::relocateRels(int fd, Elf32_Ehdr *ehdr, Elf32_Shdr *shdr) { - DEBUG_ENTER_FUNC(); - - // Loop over sections, finding relocation sections - for (int i = 0; i < ehdr->e_shnum; i++) { - - Elf32_Shdr *curShdr = &(shdr[i]); - //Elf32_Shdr *linkShdr = &(shdr[curShdr->sh_info]); - - if (curShdr->sh_type == SHT_REL && // Check for a relocation section - curShdr->sh_entsize == sizeof(Elf32_Rel) && // Check for proper relocation size - (int)curShdr->sh_link == _symtab_sect && // Check that the sh_link connects to our symbol table - curShdr->sh_info < ehdr->e_shnum && // Check that the relocated section exists - (shdr[curShdr->sh_info].sh_flags & SHF_ALLOC)) { // Check if relocated section resides in memory - if (!ShortsMan.inGeneralSegment((char *)shdr[curShdr->sh_info].sh_addr)) { // regular segment - if (!relocate(fd, curShdr->sh_offset, curShdr->sh_size, _segment)) { - return false; - } - } else { // In Shorts segment - if (!relocate(fd, curShdr->sh_offset, curShdr->sh_size, (void *)_shortsSegment->getOffset())) { - return false; - } - } - - } - } - - return true; -} - - -bool DLObject::load(int fd) { - DEBUG_ENTER_FUNC(); - - Elf32_Ehdr ehdr; // ELF header - Elf32_Phdr phdr; // Program header - Elf32_Shdr *shdr; // Section header - bool ret = true; - - if (readElfHeader(fd, &ehdr) == false) { - return false; - } - - for (int i = 0; i < ehdr.e_phnum; i++) { // Load our 2 segments - PSP_DEBUG_PRINT("Loading segment %d\n", i); - - if (readProgramHeaders(fd, &ehdr, &phdr, i) == false) - return false; - - if (!loadSegment(fd, &phdr)) - return false; - } - - if ((shdr = loadSectionHeaders(fd, &ehdr)) == NULL) - ret = false; - - if (ret && ((_symtab_sect = loadSymbolTable(fd, &ehdr, shdr)) < 0)) - ret = false; - - if (ret && (loadStringTable(fd, shdr) == false)) - ret = false; - - if (ret) - relocateSymbols((Elf32_Addr)_segment, _shortsSegment->getOffset()); // Offset by our segment allocated address - - if (ret && (relocateRels(fd, &ehdr, shdr) == false)) - ret = false; - - free(shdr); - - return ret; -} - -bool DLObject::open(const char *path) { - DEBUG_ENTER_FUNC(); - int fd; - void *ctors_start, *ctors_end; - - PSP_DEBUG_PRINT("open(\"%s\")\n", path); - - // Get the address of the global pointer - _gpVal = (unsigned int) & _gp; - PSP_DEBUG_PRINT("_gpVal is %x\n", _gpVal); - - PowerMan.beginCriticalSection(); - - if ((fd = ::open(path, O_RDONLY)) < 0) { - PSP_ERROR("%s not found.", path); - return false; - } - - // Try to load and relocate - if (!load(fd)) { - ::close(fd); - unload(); - return false; - } - - ::close(fd); - - PowerMan.endCriticalSection(); - - // flush data cache - sceKernelDcacheWritebackAll(); - - // Get the symbols for the global constructors and destructors - ctors_start = symbol("___plugin_ctors"); - ctors_end = symbol("___plugin_ctors_end"); - _dtors_start = symbol("___plugin_dtors"); - _dtors_end = symbol("___plugin_dtors_end"); - - if (ctors_start == NULL || ctors_end == NULL || _dtors_start == NULL || - _dtors_end == NULL) { - PSP_ERROR("Missing ctors/dtors."); - _dtors_start = _dtors_end = NULL; - unload(); - return false; - } - - PSP_DEBUG_PRINT("Calling constructors.\n"); - for (void (**f)(void) = (void (**)(void))ctors_start; f != ctors_end; f++) - (**f)(); - - PSP_DEBUG_PRINT("%s opened ok.\n", path); - return true; -} - -bool DLObject::close() { - DEBUG_ENTER_FUNC(); - if (_dtors_start != NULL && _dtors_end != NULL) - for (void (**f)(void) = (void (**)(void))_dtors_start; f != _dtors_end; f++) - (**f)(); - _dtors_start = _dtors_end = NULL; - unload(); - return true; -} - -void *DLObject::symbol(const char *name) { - DEBUG_ENTER_FUNC(); - PSP_DEBUG_PRINT("symbol(\"%s\")\n", name); - - if (_symtab == NULL || _strtab == NULL || _symbol_cnt < 1) { - PSP_ERROR("No symbol table loaded."); - return NULL; - } - - Elf32_Sym *s = (Elf32_Sym *)_symtab; - for (int c = _symbol_cnt; c--; s++) { - - // We can only import symbols that are global or weak in the plugin - if ((SYM_BIND(s->st_info) == STB_GLOBAL || SYM_BIND(s->st_info) == STB_WEAK) && - /*_strtab[s->st_name] == '_' && */ // Try to make this more efficient - !strcmp(name, _strtab + s->st_name)) { - - // We found the symbol - PSP_DEBUG_PRINT("=> %p\n", (void*)s->st_value); - return (void*)s->st_value; - } - } - - PSP_ERROR("Symbol \"%s\" not found.", name); - return NULL; -} - - - -ShortSegmentManager::ShortSegmentManager() { - DEBUG_ENTER_FUNC(); - _shortsStart = &__plugin_hole_start ; - _shortsEnd = &__plugin_hole_end; -} - -ShortSegmentManager::Segment *ShortSegmentManager::newSegment(int size, char *origAddr) { - DEBUG_ENTER_FUNC(); - char *lastAddress = origAddr; - Common::List<Segment *>::iterator i; - - // Find a block that fits, starting from the beginning - for (i = _list.begin(); i != _list.end(); ++i) { - char *currAddress = (*i)->getStart(); - - if ((int)(currAddress - lastAddress) >= size) break; - - lastAddress = (*i)->getEnd(); - } - - if ((Elf32_Addr)lastAddress & 3) - lastAddress += 4 - ((Elf32_Addr)lastAddress & 3); // Round up to multiple of 4 - - if (lastAddress + size > _shortsEnd) { - PSP_ERROR("No space in shorts segment for %x bytes. Last address is %p, max address is %p.\n", - size, lastAddress, _shortsEnd); - return NULL; - } - - Segment *seg = new Segment(lastAddress, size, origAddr); // Create a new segment - - if (lastAddress + size > _highestAddress) _highestAddress = lastAddress + size; // Keep track of maximum - - _list.insert(i, seg); - - PSP_DEBUG_PRINT("Shorts segment size %x allocated. End = %p. Remaining space = %x. Highest so far is %p.\n", - size, lastAddress + size, _shortsEnd - _list.back()->getEnd(), _highestAddress); - - return seg; -} - -void ShortSegmentManager::deleteSegment(ShortSegmentManager::Segment *seg) { - DEBUG_ENTER_FUNC(); - PSP_DEBUG_PRINT("Deleting shorts segment from %p to %p.\n\n", seg->getStart(), seg->getEnd()); - _list.remove(seg); - delete seg; -} - -static char dlerr[MAXDLERRLEN]; - -void *dlopen(const char *filename, int flags) { - DLObject *obj = new DLObject(dlerr); - if (obj->open(filename)) - return (void *)obj; - delete obj; - return NULL; -} - -int dlclose(void *handle) { - DLObject *obj = (DLObject *)handle; - if (obj == NULL) { - strcpy(dlerr, "Handle is NULL."); - return -1; - } - if (obj->close()) { - delete obj; - return 0; - } - return -1; -} - -void *dlsym(void *handle, const char *symbol) { - if (handle == NULL) { - strcpy(dlerr, "Handle is NULL."); - return NULL; - } - return ((DLObject *)handle)->symbol(symbol); -} - -const char *dlerror() { - return dlerr; -} - -void dlforgetsyms(void *handle) { - if (handle != NULL) - ((DLObject *)handle)->discard_symtab(); -} - - -#endif /* DYNAMIC_MODULES && __PSP__ */ diff --git a/backends/platform/psp/thread.cpp b/backends/platform/psp/thread.cpp index 916b1e553b..e757c2f575 100644 --- a/backends/platform/psp/thread.cpp +++ b/backends/platform/psp/thread.cpp @@ -18,8 +18,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/backends/platform/psp/osys_psp.h $ - * $Id: osys_psp.h 49173 2010-05-24 03:05:17Z bluddy $ + * $URL$ + * $Id$ * */ diff --git a/backends/platform/psp/thread.h b/backends/platform/psp/thread.h index de1c10a2aa..44394af40a 100644 --- a/backends/platform/psp/thread.h +++ b/backends/platform/psp/thread.h @@ -18,8 +18,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/backends/platform/psp/portdefs.h $ - * $Id: portdefs.h 38687 2009-02-21 12:08:52Z joostp $ + * $URL$ + * $Id$ * */ diff --git a/backends/platform/samsungtv/main.cpp b/backends/platform/samsungtv/main.cpp index 2c025b750c..a6b8376912 100644 --- a/backends/platform/samsungtv/main.cpp +++ b/backends/platform/samsungtv/main.cpp @@ -43,7 +43,7 @@ extern "C" int Game_Main(char *path, char *) { // Invoke the actual ScummVM main entry point: int res = scummvm_main(0, 0); - ((OSystem_SDL *)g_system)->deinit(); + g_system->quit(); // TODO: Consider removing / replacing this! return res; } diff --git a/backends/platform/sdl/graphics.cpp b/backends/platform/sdl/graphics.cpp index a97a153f3c..aec90d1352 100644 --- a/backends/platform/sdl/graphics.cpp +++ b/backends/platform/sdl/graphics.cpp @@ -1693,6 +1693,7 @@ void OSystem_SDL::drawMouse() { SDL_Rect dst; int scale; + int width, height; int hotX, hotY; dst.x = _mouseCurState.x; @@ -1700,12 +1701,16 @@ void OSystem_SDL::drawMouse() { if (!_overlayVisible) { scale = _videoMode.scaleFactor; + width = _videoMode.screenWidth; + height = _videoMode.screenHeight; dst.w = _mouseCurState.vW; dst.h = _mouseCurState.vH; hotX = _mouseCurState.vHotX; hotY = _mouseCurState.vHotY; } else { scale = 1; + width = _videoMode.overlayWidth; + height = _videoMode.overlayHeight; dst.w = _mouseCurState.rW; dst.h = _mouseCurState.rH; hotX = _mouseCurState.rHotX; diff --git a/backends/platform/sdl/main.cpp b/backends/platform/sdl/main.cpp index 52bbb59165..ef95a6d256 100644 --- a/backends/platform/sdl/main.cpp +++ b/backends/platform/sdl/main.cpp @@ -64,7 +64,7 @@ int main(int argc, char *argv[]) { // Invoke the actual ScummVM main entry point: int res = scummvm_main(argc, argv); - ((OSystem_SDL *)g_system)->deinit(); + g_system->quit(); // TODO: Consider removing / replacing this! return res; } diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp index 6686249416..5c50a43daf 100644 --- a/backends/platform/sdl/sdl.cpp +++ b/backends/platform/sdl/sdl.cpp @@ -483,7 +483,7 @@ bool OSystem_SDL::getFeatureState(Feature f) { } } -void OSystem_SDL::deinit() { +void OSystem_SDL::quit() { if (_cdrom) { SDL_CDStop(_cdrom); SDL_CDClose(_cdrom); @@ -505,14 +505,10 @@ void OSystem_SDL::deinit() { SDL_Quit(); - // Event Manager requires save manager for storing + // Even Manager requires save manager for storing // recorded events delete getEventManager(); delete _savefile; -} - -void OSystem_SDL::quit() { - deinit(); #if !defined(SAMSUNGTV) exit(0); diff --git a/backends/platform/sdl/sdl.h b/backends/platform/sdl/sdl.h index 5c901ba711..ace07f349e 100644 --- a/backends/platform/sdl/sdl.h +++ b/backends/platform/sdl/sdl.h @@ -202,8 +202,6 @@ public: // Quit virtual void quit(); // overloaded by CE backend - void deinit(); - virtual void getTimeAndDate(TimeDate &t) const; virtual Common::TimerManager *getTimerManager(); diff --git a/backends/plugins/arm-loader.cpp b/backends/plugins/arm-loader.cpp new file mode 100644 index 0000000000..7e8269220b --- /dev/null +++ b/backends/plugins/arm-loader.cpp @@ -0,0 +1,169 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#if defined(DYNAMIC_MODULES) && defined(ARM_TARGET) + +#include "backends/fs/ds/ds-fs.h" +#include "elf-loader.h" +#include "dsmain.h" +#include "arm-loader.h" + +#define __DEBUG_PLUGINS__ + +#ifdef __DEBUG_PLUGINS__ +#define DBG(x,...) consolePrintf(x, ## __VA_ARGS__) +#else +#define DBG(x,...) +#endif + +#define seterror(x,...) consolePrintf(x, ## __VA_ARGS__) + +/** + * Follow the instruction of a relocation section. + * + * @param DLFile SeekableReadStream of File + * @param offset Offset into the File + * @param size Size of relocation section + * + */ +bool ARMDLObject::relocate(Common::SeekableReadStream* DLFile, unsigned long offset, unsigned long size, void *relSegment) { + Elf32_Rel *rel = NULL; //relocation entry + + // Allocate memory for relocation table + if (!(rel = (Elf32_Rel *)malloc(size))) { + seterror("Out of memory."); + return false; + } + + // Read in our relocation table + if (DLFile->seek(offset, SEEK_SET) < 0 || + DLFile->read(rel, size) != (ssize_t)size) { + seterror("Relocation table load failed."); + free(rel); + return false; + } + + // Treat each relocation entry. Loop over all of them + int cnt = size / sizeof(*rel); + + DBG("Loaded relocation table. %d entries. base address=%p\n", cnt, relSegment); + + int a = 0; + unsigned int relocation = 0; + + // Loop over relocation entries + for (int i = 0; i < cnt; i++) { + + // Get the symbol this relocation entry is referring to + Elf32_Sym *sym = (Elf32_Sym *)(_symtab) + (REL_INDEX(rel[i].r_info)); + + // Get the target instruction in the code + unsigned int *target = (unsigned int *)((char *)relSegment + rel[i].r_offset); + + unsigned int origTarget = *target; //Save for debugging + + // Act differently based on the type of relocation + switch (REL_TYPE(rel[i].r_info)) { + + case R_ARM_ABS32: + if (sym->st_shndx < SHN_LOPROC) { // Only shift for plugin section. + a = *target; // Get full 32 bits of addend + relocation = a + (Elf32_Addr)_segment; // Shift by main offset + + *target = relocation; + + DBG("R_ARM_ABS32: i=%d, a=%x, origTarget=%x, target=%x\n", i, a, origTarget, *target); + } + break; + + case R_ARM_THM_CALL: + DBG("R_ARM_THM_CALL: PC-relative jump, ld takes care of necessary relocation work for us.\n"); + break; + + case R_ARM_CALL: + DBG("R_ARM_CALL: PC-relative jump, ld takes care of necessary relocation work for us.\n"); + break; + + case R_ARM_JUMP24: + DBG("R_ARM_JUMP24: PC-relative jump, ld takes care of all relocation work for us.\n"); + break; + + case R_ARM_TARGET1: + if (sym->st_shndx < SHN_LOPROC) { // Only shift for plugin section. + a = *target; // Get full 32 bits of addend + relocation = a + (Elf32_Addr)_segment; // Shift by main offset + + *target = relocation; + + DBG("R_ARM_TARGET1: i=%d, a=%x, origTarget=%x, target=%x\n", i, a, origTarget, *target); + DBG("Make sure --target1-abs is a flag to LD!\n"); + } + break; + + case R_ARM_V4BX: + DBG("R_ARM_V4BX: No relocation calculation necessary.\n"); + break; + + default: + seterror("Unknown relocation type %d.", REL_TYPE(rel[i].r_info)); + free(rel); + return false; + } + + } + + free(rel); + return true; +} + +bool ARMDLObject::relocateRels(Common::SeekableReadStream* DLFile, Elf32_Ehdr *ehdr, Elf32_Shdr *shdr) { + + // Loop over sections, finding relocation sections + for (int i = 0; i < ehdr->e_shnum; i++) { + + Elf32_Shdr *curShdr = &(shdr[i]); + + if ((curShdr->sh_type == SHT_REL || curShdr->sh_type == SHT_RELA) && // Check for a relocation section + curShdr->sh_entsize == sizeof(Elf32_Rel) && // Check for proper relocation size + (int)curShdr->sh_link == _symtab_sect && // Check that the sh_link connects to our symbol table + curShdr->sh_info < ehdr->e_shnum && // Check that the relocated section exists + (shdr[curShdr->sh_info].sh_flags & SHF_ALLOC)) { // Check if relocated section resides in memory + + if (curShdr->sh_type == SHT_RELA) { + seterror("RELA entries not supported yet!\n"); + return false; + } + + if (!relocate(DLFile, curShdr->sh_offset, curShdr->sh_size, _segment)) { + return false; + } + + } + } + + return true; +} + +#endif /* defined(DYNAMIC_MODULES) && defined(ARM_TARGET) */ diff --git a/backends/plugins/arm-loader.h b/backends/plugins/arm-loader.h new file mode 100644 index 0000000000..7f7a1f7f4b --- /dev/null +++ b/backends/plugins/arm-loader.h @@ -0,0 +1,37 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "backends/fs/ds/ds-fs.h" +#include "elf-loader.h" +#include "dsmain.h" + +class ARMDLObject : public DLObject { +protected: + bool relocate(Common::SeekableReadStream* DLFile, unsigned long offset, unsigned long size, void *relSegment); + bool relocateRels(Common::SeekableReadStream* DLFile, Elf32_Ehdr *ehdr, Elf32_Shdr *shdr); + +public: + ARMDLObject() : DLObject() {} +}; diff --git a/backends/plugins/ds/ds-provider.cpp b/backends/plugins/ds/ds-provider.cpp new file mode 100644 index 0000000000..7365a2e6e9 --- /dev/null +++ b/backends/plugins/ds/ds-provider.cpp @@ -0,0 +1,45 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#if defined(DYNAMIC_MODULES) && defined(__DS__) + +#include "backends/plugins/arm-loader.h" +#include "backends/plugins/elf-provider.h" +#include "backends/plugins/ds/ds-provider.h" + + +class DSPlugin : public ELFPlugin { +public: + DSPlugin(const Common::String &filename) : ELFPlugin(filename) {} + + DLObject *makeDLObject() { return new ARMDLObject(); } +}; + +Plugin* DSPluginProvider::createPlugin(const Common::FSNode &node) const { + return new DSPlugin(node.getPath()); +} + +#endif // defined(DYNAMIC_MODULES) && defined(ELF_LOADER_TARGET) + diff --git a/backends/plugins/ds/ds-provider.h b/backends/plugins/ds/ds-provider.h new file mode 100644 index 0000000000..462c3e3b63 --- /dev/null +++ b/backends/plugins/ds/ds-provider.h @@ -0,0 +1,32 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "backends/plugins/elf-provider.h" + +class DSPluginProvider : public ELFPluginProvider { + Plugin* createPlugin(const Common::FSNode &node) const; +}; + + diff --git a/backends/plugins/ds/plugin.ld b/backends/plugins/ds/plugin.ld new file mode 100644 index 0000000000..2b2bca9745 --- /dev/null +++ b/backends/plugins/ds/plugin.ld @@ -0,0 +1,218 @@ +OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", + "elf32-littlearm") +OUTPUT_ARCH(arm) + +/* PHDRS specifies ELF Program Headers (or segments) to the plugin linker */ +PHDRS { + plugin PT_LOAD ; /* Specifies that the plugin segment should be loaded from file */ +} + +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + . = 0; + .interp : { *(.interp) } : plugin + .note.gnu.build-id : { *(.note.gnu.build-id) } + .hash : { *(.hash) } + .gnu.hash : { *(.gnu.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rel.dyn : + { + *(.rel.init) + *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*) + *(.rel.fini) + *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*) + *(.rel.data.rel.ro* .rel.gnu.linkonce.d.rel.ro.*) + *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*) + *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*) + *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*) + *(.rel.ctors) + *(.rel.dtors) + *(.rel.got) + *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*) + PROVIDE_HIDDEN (__rel_iplt_start = .); + *(.rel.iplt) + PROVIDE_HIDDEN (__rel_iplt_end = .); + PROVIDE_HIDDEN (__rela_iplt_start = .); + PROVIDE_HIDDEN (__rela_iplt_end = .); + } + .rela.dyn : + { + *(.rela.init) + *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) + *(.rela.fini) + *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) + *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) + *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) + *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) + *(.rela.ctors) + *(.rela.dtors) + *(.rela.got) + *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) + PROVIDE_HIDDEN (__rel_iplt_start = .); + PROVIDE_HIDDEN (__rel_iplt_end = .); + PROVIDE_HIDDEN (__rela_iplt_start = .); + *(.rela.iplt) + PROVIDE_HIDDEN (__rela_iplt_end = .); + } + .rel.plt : + { + *(.rel.plt) + } + .rela.plt : + { + *(.rela.plt) + } + .init : + { + KEEP (*(.init)) + } =0 + .plt : { *(.plt) } + .iplt : { *(.iplt) } + .text : + { + *(.text.unlikely .text.*_unlikely) + *(.text .stub .text.* .gnu.linkonce.t.*) + /* .gnu.warning sections are handled specially by elf32.em. */ + *(.gnu.warning) + *(.glue_7t) *(.glue_7) *(.vfp11_veneer) *(.v4_bx) + } =0 + .fini : + { + KEEP (*(.fini)) + } =0 + PROVIDE (__etext = .); + PROVIDE (_etext = .); + PROVIDE (etext = .); + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } + .rodata1 : { *(.rodata1) } + .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } + __exidx_start = .; + .ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } + __exidx_end = .; + .eh_frame_hdr : { *(.eh_frame_hdr) } + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) } + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)); + /* Exception handling */ + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) } + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } + /* Thread Local Storage sections */ + .tdata : { *(.tdata .tdata.* .gnu.linkonce.td.*) } + .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + } + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE_HIDDEN (__fini_array_end = .); + } + .ctors : + { + ___plugin_ctors = .; + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + ___plugin_ctors_end = .; + } + .dtors : + { + ___plugin_dtors = .; + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + ___plugin_dtors_end = .; + } + .jcr : { KEEP (*(.jcr)) } + .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro* .gnu.linkonce.d.rel.ro.*) } + .dynamic : { *(.dynamic) } + .got : { *(.got.plt) *(.igot.plt) *(.got) *(.igot) } + .data : + { + __data_start = . ; + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } + .data1 : { *(.data1) } + _edata = .; PROVIDE (edata = .); + __bss_start = .; + __bss_start__ = .; + .bss : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + /* Align here to ensure that the .bss section occupies space up to + _end. Align after .bss to ensure correct alignment even if the + .bss section disappears because there are no input sections. + FIXME: Why do we need it? When there is no .bss section, we don't + pad the .data section. */ + . = ALIGN(. != 0 ? 32 / 8 : 1); + } + _bss_end__ = . ; __bss_end__ = . ; + . = ALIGN(32 / 8); + . = ALIGN(32 / 8); + __end__ = . ; + _end = .; PROVIDE (end = .); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3 */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + .stack 0x80000 : + { + _stack = .; + *(.stack) + } + .ARM.attributes 0 : { KEEP (*(.ARM.attributes)) KEEP (*(.gnu.attributes)) } + .note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) } + /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } +} diff --git a/backends/plugins/elf-loader.cpp b/backends/plugins/elf-loader.cpp new file mode 100644 index 0000000000..9787c880ae --- /dev/null +++ b/backends/plugins/elf-loader.cpp @@ -0,0 +1,413 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#if defined(DYNAMIC_MODULES) && defined(ELF_LOADER_TARGET) + +#include <string.h> +#include <stdarg.h> +#include <stdio.h> +#include <malloc.h> // for memalign() (Linux specific) +#include <unistd.h> +#include <sys/fcntl.h> +#include <sys/_default_fcntl.h> // FIXME: Why do we need this DevKitPro specific header? + +#include "common/file.h" +#include "common/fs.h" +#include "elf-loader.h" + +#ifdef __PSP__ +#include "backends/platform/psp/powerman.h" +#include "psputils.h" +#endif + +#ifdef __DS__ +#include <nds.h> +#endif + +#define __DEBUG_PLUGINS__ + +#ifdef __DEBUG_PLUGINS__ +#define DBG(x,...) printf(x, ## __VA_ARGS__) +#else +#define DBG(x,...) +#endif + +#define seterror(x,...) printf(x, ## __VA_ARGS__) + +/** + * Flushes the data cache (Platform Specific). + */ +static void flushDataCache() { +#ifdef __DS__ + DC_FlushAll(); +#endif +#ifdef __PLAYSTATION2__ + FlushCache(0); + FlushCache(2); +#endif +#ifdef __PSP__ + sceKernelDcacheWritebackAll(); +#endif +} + +// Expel the symbol table from memory +void DLObject::discard_symtab() { + free(_symtab); + free(_strtab); + _symtab = NULL; + _strtab = NULL; + _symbol_cnt = 0; +} + +// Unload all objects from memory +void DLObject::unload() { + discard_symtab(); + free(_segment); + _segment = NULL; +} + +bool DLObject::readElfHeader(Common::SeekableReadStream* DLFile, Elf32_Ehdr *ehdr) { + + // Start reading the elf header. Check for errors + if (DLFile->read(ehdr, sizeof(*ehdr)) != sizeof(*ehdr) || + memcmp(ehdr->e_ident, ELFMAG, SELFMAG) || // Check MAGIC + ehdr->e_type != ET_EXEC || // Check for executable +#ifdef ARM_TARGET + ehdr->e_machine != EM_ARM || // Check for ARM machine type +#endif +#ifdef MIPS_TARGET + ehdr->e_machine != EM_MIPS || // Check for MIPS machine type +#endif + ehdr->e_phentsize < sizeof(Elf32_Phdr) || // Check for size of program header + ehdr->e_shentsize != sizeof(Elf32_Shdr)) { // Check for size of section header + seterror("Invalid file type."); + return false; + } + + DBG("phoff = %d, phentsz = %d, phnum = %d\n", + ehdr->e_phoff, ehdr->e_phentsize, ehdr->e_phnum); + + return true; +} + +bool DLObject::readProgramHeaders(Common::SeekableReadStream* DLFile, Elf32_Ehdr *ehdr, Elf32_Phdr *phdr, int num) { + + // Read program header + if (DLFile->seek(ehdr->e_phoff + sizeof(*phdr)*num, SEEK_SET) < 0 || + DLFile->read(phdr, sizeof(*phdr)) != sizeof(*phdr)) { + seterror("Program header load failed."); + return false; + } + + // Check program header values + if (phdr->p_type != PT_LOAD || phdr->p_filesz > phdr->p_memsz) { + seterror("Invalid program header."); + return false; + } + + DBG("offs = %x, filesz = %x, memsz = %x, align = %x\n", + phdr->p_offset, phdr->p_filesz, phdr->p_memsz, phdr->p_align); + + return true; + +} + +bool DLObject::loadSegment(Common::SeekableReadStream* DLFile, Elf32_Phdr *phdr) { + + char *baseAddress = 0; + + // Attempt to allocate memory for segment + int extra = phdr->p_vaddr % phdr->p_align; // Get extra length TODO: check logic here + DBG("extra mem is %x\n", extra); + + if (!(_segment = (char *)memalign(phdr->p_align, phdr->p_memsz + extra))) { + seterror("Out of memory.\n"); + return false; + } + + DBG("allocated segment @ %p\n", _segment); + + // Get offset to load segment into + baseAddress = (char *)_segment + phdr->p_vaddr; + _segmentSize = phdr->p_memsz + extra; + + // Set bss segment to 0 if necessary (assumes bss is at the end) + if (phdr->p_memsz > phdr->p_filesz) { + DBG("Setting %p to %p to 0 for bss\n", baseAddress + phdr->p_filesz, baseAddress + phdr->p_memsz); + memset(baseAddress + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz); + } + + DBG("Reading the segment into memory\n"); + + // Read the segment into memory + if (DLFile->seek(phdr->p_offset, SEEK_SET) < 0 || + DLFile->read(baseAddress, phdr->p_filesz) != (ssize_t)phdr->p_filesz) { + seterror("Segment load failed."); + return false; + } + + DBG("Segment has been read into memory\n"); + + return true; +} + +Elf32_Shdr * DLObject::loadSectionHeaders(Common::SeekableReadStream* DLFile, Elf32_Ehdr *ehdr) { + + Elf32_Shdr *shdr = NULL; + + // Allocate memory for section headers + if (!(shdr = (Elf32_Shdr *)malloc(ehdr->e_shnum * sizeof(*shdr)))) { + seterror("Out of memory."); + return NULL; + } + + // Read from file into section headers + if (DLFile->seek(ehdr->e_shoff, SEEK_SET) < 0 || + DLFile->read(shdr, ehdr->e_shnum * sizeof(*shdr)) != + (ssize_t)(ehdr->e_shnum * sizeof(*shdr))) { + seterror("Section headers load failed."); + return NULL; + } + + return shdr; +} + +int DLObject::loadSymbolTable(Common::SeekableReadStream* DLFile, Elf32_Ehdr *ehdr, Elf32_Shdr *shdr) { + + // Loop over sections, looking for symbol table linked to a string table + for (int i = 0; i < ehdr->e_shnum; i++) { + if (shdr[i].sh_type == SHT_SYMTAB && + shdr[i].sh_entsize == sizeof(Elf32_Sym) && + shdr[i].sh_link < ehdr->e_shnum && + shdr[shdr[i].sh_link].sh_type == SHT_STRTAB && + _symtab_sect < 0) { + _symtab_sect = i; + } + } + + // Check for no symbol table + if (_symtab_sect < 0) { + seterror("No symbol table."); + return -1; + } + + DBG("Symbol section at section %d, size %x\n", _symtab_sect, shdr[_symtab_sect].sh_size); + + // Allocate memory for symbol table + if (!(_symtab = malloc(shdr[_symtab_sect].sh_size))) { + seterror("Out of memory."); + return -1; + } + + // Read symbol table into memory + if (DLFile->seek(shdr[_symtab_sect].sh_offset, SEEK_SET) < 0 || + DLFile->read(_symtab, shdr[_symtab_sect].sh_size) != + (ssize_t)shdr[_symtab_sect].sh_size) { + seterror("Symbol table load failed."); + return -1; + } + + // Set number of symbols + _symbol_cnt = shdr[_symtab_sect].sh_size / sizeof(Elf32_Sym); + DBG("Loaded %d symbols.\n", _symbol_cnt); + + return _symtab_sect; + +} + +bool DLObject::loadStringTable(Common::SeekableReadStream* DLFile, Elf32_Shdr *shdr) { + + int string_sect = shdr[_symtab_sect].sh_link; + + // Allocate memory for string table + if (!(_strtab = (char *)malloc(shdr[string_sect].sh_size))) { + seterror("Out of memory."); + return false; + } + + // Read string table into memory + if (DLFile->seek(shdr[string_sect].sh_offset, SEEK_SET) < 0 || + DLFile->read(_strtab, shdr[string_sect].sh_size) != + (ssize_t)shdr[string_sect].sh_size) { + seterror("Symbol table strings load failed."); + return false; + } + + return true; +} + +void DLObject::relocateSymbols(Elf32_Addr offset) { + + int mainCount = 0; + int shortsCount= 0; + + // Loop over symbols, add relocation offset + Elf32_Sym *s = (Elf32_Sym *)_symtab; + for (int c = _symbol_cnt; c--; s++) { + + // Make sure we don't relocate special valued symbols + if (s->st_shndx < SHN_LOPROC) { + mainCount++; + s->st_value += offset; + if (s->st_value < (Elf32_Addr)_segment || s->st_value > (Elf32_Addr)_segment + _segmentSize) + seterror("Symbol out of bounds! st_value = %x\n", s->st_value); + } + } +} + +bool DLObject::load(Common::SeekableReadStream* DLFile) { + Elf32_Ehdr ehdr; + Elf32_Phdr phdr; + Elf32_Shdr *shdr; + bool ret = true; + + if (readElfHeader(DLFile, &ehdr) == false) { + return false; + } + + for (int i = 0; i < ehdr.e_phnum; i++) { //Load our segments + + DBG("Loading segment %d\n", i); + + if (readProgramHeaders(DLFile, &ehdr, &phdr, i) == false) + return false; + + if (!loadSegment(DLFile, &phdr)) + return false; + } + + if ((shdr = loadSectionHeaders(DLFile, &ehdr)) == NULL) + ret = false; + + if (ret && ((_symtab_sect = loadSymbolTable(DLFile, &ehdr, shdr)) < 0)) + ret = false; + + if (ret && (loadStringTable(DLFile, shdr) == false)) + ret = false; + + if (ret) + relocateSymbols((Elf32_Addr)_segment); // Offset by our segment allocated address + + if (ret && (relocateRels(DLFile, &ehdr, shdr) == false)) + ret = false; + + free(shdr); + + return ret; + +} + +bool DLObject::open(const char *path) { + + Common::SeekableReadStream* DLFile; + void *ctors_start, *ctors_end; + +#ifdef __PSP__ + PowerMan.beginCriticalSection(); +#endif + + DBG("open(\"%s\")\n", path); + + Common::FSNode file(path); + + if (!(DLFile = file.createReadStream())) { + seterror("%s not found.", path); + return false; + } + + DBG("%s found!\n", path); + + /*Try to load and relocate*/ + if (!load(DLFile)) { + unload(); + return false; + } + + DBG("loaded!/n"); + +#ifdef __PSP__ + PowerMan.endCriticalSection(); +#endif + + flushDataCache(); + + ctors_start = symbol("___plugin_ctors"); + ctors_end = symbol("___plugin_ctors_end"); + _dtors_start = symbol("___plugin_dtors"); + _dtors_end = symbol("___plugin_dtors_end"); + + if (ctors_start == NULL || ctors_end == NULL || _dtors_start == NULL || + _dtors_end == NULL) { + seterror("Missing ctors/dtors."); + _dtors_start = _dtors_end = NULL; + unload(); + return false; + } + + DBG(("Calling constructors.\n")); + for (void (**f)(void) = (void (**)(void))ctors_start; f != ctors_end; f++) + (**f)(); + + DBG(("%s opened ok.\n", path)); + + return true; +} + +bool DLObject::close() { + if (_dtors_start != NULL && _dtors_end != NULL) + for (void (**f)(void) = (void (**)(void))_dtors_start; f != _dtors_end; f++) + (**f)(); + _dtors_start = _dtors_end = NULL; + unload(); + return true; +} + +void *DLObject::symbol(const char *name) { + DBG(("symbol(\"%s\")\n", name)); + + if (_symtab == NULL || _strtab == NULL || _symbol_cnt < 1) { + seterror("No symbol table loaded."); + return NULL; + } + + Elf32_Sym *s = (Elf32_Sym *)_symtab; + for (int c = _symbol_cnt; c--; s++) + + // We can only import symbols that are global or weak in the plugin + if ((SYM_BIND(s->st_info) == STB_GLOBAL || SYM_BIND(s->st_info) == STB_WEAK) && + !strcmp(name, _strtab + s->st_name)) { + + // We found the symbol + DBG("=> %p\n", (void*)s->st_value); + return (void*)s->st_value; + } + + // We didn't find the symbol + seterror("Symbol \"%s\" not found.", name); + return NULL; +} + +#endif /* defined(DYNAMIC_MODULES) && defined(ELF_LOADER_TARGET) */ + diff --git a/backends/plugins/elf-loader.h b/backends/plugins/elf-loader.h new file mode 100644 index 0000000000..1d30aa0c3b --- /dev/null +++ b/backends/plugins/elf-loader.h @@ -0,0 +1,67 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef ELF_LOADER_H +#define ELF_LOADER_H + +#include "elf32.h" +#include "common/stream.h" +#include "backends/plugins/dynamic-plugin.h" + +class DLObject { +protected: + void *_segment, *_symtab; + char *_strtab; + int _symbol_cnt; + int _symtab_sect; + void *_dtors_start, *_dtors_end; + + int _segmentSize; + + //void seterror(const char *fmt, ...); + virtual void unload(); + virtual bool relocate(Common::SeekableReadStream* DLFile, unsigned long offset, unsigned long size, void *relSegment) = 0; + bool load(Common::SeekableReadStream* DLFile); + + bool readElfHeader(Common::SeekableReadStream* DLFile, Elf32_Ehdr *ehdr); + bool readProgramHeaders(Common::SeekableReadStream* DLFile, Elf32_Ehdr *ehdr, Elf32_Phdr *phdr, int num); + virtual bool loadSegment(Common::SeekableReadStream* DLFile, Elf32_Phdr *phdr); + Elf32_Shdr *loadSectionHeaders(Common::SeekableReadStream* DLFile, Elf32_Ehdr *ehdr); + int loadSymbolTable(Common::SeekableReadStream* DLFile, Elf32_Ehdr *ehdr, Elf32_Shdr *shdr); + bool loadStringTable(Common::SeekableReadStream* DLFile, Elf32_Shdr *shdr); + virtual void relocateSymbols(Elf32_Addr offset); + virtual bool relocateRels(Common::SeekableReadStream* DLFile, Elf32_Ehdr *ehdr, Elf32_Shdr *shdr) = 0; + +public: + bool open(const char *path); + bool close(); + void *symbol(const char *name); + void discard_symtab(); + + DLObject() : _segment(NULL), _symtab(NULL), _strtab(NULL), _symbol_cnt(0), + _symtab_sect(-1), _dtors_start(NULL), _dtors_end(NULL), _segmentSize(0) {} +}; + +#endif /* ELF_LOADER_H */ diff --git a/backends/plugins/elf-provider.cpp b/backends/plugins/elf-provider.cpp new file mode 100644 index 0000000000..e6edd4c578 --- /dev/null +++ b/backends/plugins/elf-provider.cpp @@ -0,0 +1,105 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#if defined(DYNAMIC_MODULES) && defined(ELF_LOADER_TARGET) + +#include "backends/plugins/elf-provider.h" +#include "backends/plugins/dynamic-plugin.h" +#include "common/fs.h" + +#include "backends/plugins/elf-loader.h" + +DynamicPlugin::VoidFunc ELFPlugin::findSymbol(const char *symbol) { + void *func; + bool handleNull; + if (_dlHandle == NULL) { + func = NULL; + handleNull = true; + } else { + func = _dlHandle->symbol(symbol); + } + if (!func) { + if (handleNull) { + warning("Failed loading symbol '%s' from plugin '%s' (Handle is NULL)", symbol, _filename.c_str()); + } else { + warning("Failed loading symbol '%s' from plugin '%s'", symbol, _filename.c_str()); + } + } + + // FIXME HACK: This is a HACK to circumvent a clash between the ISO C++ + // standard and POSIX: ISO C++ disallows casting between function pointers + // and data pointers, but dlsym always returns a void pointer. For details, + // see e.g. <http://www.trilithium.com/johan/2004/12/problem-with-dlsym/>. + assert(sizeof(VoidFunc) == sizeof(func)); + VoidFunc tmp; + memcpy(&tmp, &func, sizeof(VoidFunc)); + return tmp; +} + +bool ELFPlugin::loadPlugin() { + assert(!_dlHandle); + DLObject *obj = makeDLObject(); + if (obj->open(_filename.c_str())) { + _dlHandle = obj; + } else { + delete obj; + _dlHandle = NULL; + } + + if (!_dlHandle) { + warning("Failed loading plugin '%s'", _filename.c_str()); + return false; + } + + bool ret = DynamicPlugin::loadPlugin(); + + if (ret && _dlHandle) { + _dlHandle->discard_symtab(); + } + + return ret; +}; + +void ELFPlugin::unloadPlugin() { + DynamicPlugin::unloadPlugin(); + if (_dlHandle) { + if (!_dlHandle->close()) { + warning("Failed unloading plugin '%s'", _filename.c_str()); + } + delete _dlHandle; + _dlHandle = 0; + } +} + +bool ELFPluginProvider::isPluginFilename(const Common::FSNode &node) const { + // Check the plugin suffix + Common::String filename = node.getName(); + if (!filename.hasSuffix(".PLG") && !filename.hasSuffix(".plg") && !filename.hasSuffix(".PLUGIN") && !filename.hasSuffix(".plugin")) { + return false; + } + return true; +} + +#endif // defined(DYNAMIC_MODULES) && defined(ELF_LOADER_TARGET) diff --git a/backends/plugins/elf-provider.h b/backends/plugins/elf-provider.h new file mode 100644 index 0000000000..e0382c3e45 --- /dev/null +++ b/backends/plugins/elf-provider.h @@ -0,0 +1,70 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef BACKENDS_PLUGINS_ELF_PROVIDER_H +#define BACKENDS_PLUGINS_ELF_PROVIDER_H + +#include "base/plugins.h" +#include "backends/plugins/dynamic-plugin.h" +#include "common/fs.h" + +#include "backends/plugins/elf-loader.h" + +#if defined(DYNAMIC_MODULES) && defined(ELF_LOADER_TARGET) + +class ELFPlugin : public DynamicPlugin { +protected: + DLObject *_dlHandle; + Common::String _filename; + + virtual VoidFunc findSymbol(const char *symbol); + +public: + ELFPlugin(const Common::String &filename) + : _dlHandle(0), _filename(filename) {} + + ~ELFPlugin() { + if (_dlHandle) + unloadPlugin(); + } + + virtual DLObject *makeDLObject() = 0; + + bool loadPlugin(); + void unloadPlugin(); + +}; + +class ELFPluginProvider : public FilePluginProvider { +protected: + virtual Plugin* createPlugin(const Common::FSNode &node) const = 0; + + bool isPluginFilename(const Common::FSNode &node) const; + +}; + +#endif // defined(DYNAMIC_MODULES) && defined(ELF_LOADER_TARGET) + +#endif /* BACKENDS_PLUGINS_ELF_PROVIDER_H */ diff --git a/backends/plugins/elf32.h b/backends/plugins/elf32.h new file mode 100644 index 0000000000..5dec1d2e58 --- /dev/null +++ b/backends/plugins/elf32.h @@ -0,0 +1,229 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef BACKENDS_ELF_H +#define BACKENDS_ELF_H + +/* ELF stuff */ + +typedef unsigned short Elf32_Half, Elf32_Section; +typedef unsigned int Elf32_Word, Elf32_Addr, Elf32_Off; +typedef signed int Elf32_Sword; +typedef Elf32_Half Elf32_Versym; + +#define EI_NIDENT (16) +#define SELFMAG 6 + +/* ELF File format structures. Look up ELF structure for more details */ + +// ELF header (contains info about the file) +typedef struct { + unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ + Elf32_Half e_type; /* Object file type */ + Elf32_Half e_machine; /* Architecture */ + Elf32_Word e_version; /* Object file version */ + Elf32_Addr e_entry; /* Entry point virtual address */ + Elf32_Off e_phoff; /* Program header table file offset */ + Elf32_Off e_shoff; /* Section header table file offset */ + Elf32_Word e_flags; /* Processor-specific flags */ + Elf32_Half e_ehsize; /* ELF header size in bytes */ + Elf32_Half e_phentsize; /* Program header table entry size */ + Elf32_Half e_phnum; /* Program header table entry count */ + Elf32_Half e_shentsize; /* Section header table entry size */ + Elf32_Half e_shnum; /* Section header table entry count */ + Elf32_Half e_shstrndx; /* Section header string table index */ +} Elf32_Ehdr; + +// Should be in e_ident +#define ELFMAG "\177ELF\1\1" /* ELF Magic number */ + +// e_type values +#define ET_NONE 0 /* no file type */ +#define ET_REL 1 /* relocatable */ +#define ET_EXEC 2 /* executable */ +#define ET_DYN 3 /* shared object */ +#define ET_CORE 4 /* core file */ + +// e_machine values +#define EM_MIPS 8 +#define EM_ARM 40 + +// Program header (contains info about segment) +typedef struct { + Elf32_Word p_type; /* Segment type */ + Elf32_Off p_offset; /* Segment file offset */ + Elf32_Addr p_vaddr; /* Segment virtual address */ + Elf32_Addr p_paddr; /* Segment physical address */ + Elf32_Word p_filesz; /* Segment size in file */ + Elf32_Word p_memsz; /* Segment size in memory */ + Elf32_Word p_flags; /* Segment flags */ + Elf32_Word p_align; /* Segment alignment */ +} Elf32_Phdr; + +// p_type values +#define PT_NULL 0 /* ignored */ +#define PT_LOAD 1 /* loadable segment */ +#define PT_DYNAMIC 2 /* dynamic linking info */ +#define PT_INTERP 3 /* info about interpreter */ +#define PT_NOTE 4 /* note segment */ +#define PT_SHLIB 5 /* reserved */ +#define PT_PHDR 6 /* Program header table */ +#define PT_MIPS_REGINFO 0x70000000 /* Register usage info for MIPS */ +#define PT_ARM_ARCHEXT 0x70000000 /* Platform architecture compatibility info for ARM */ +#define PT_ARM_EXIDX 0x70000001 /* Exception unwind tables for ARM */ + +// p_flags value +#define PF_X 1 /* execute */ +#define PF_W 2 /* write */ +#define PF_R 4 /* read */ + +// Section header (contains info about section) +typedef struct { + Elf32_Word sh_name; /* Section name (string tbl index) */ + Elf32_Word sh_type; /* Section type */ + Elf32_Word sh_flags; /* Section flags */ + Elf32_Addr sh_addr; /* Section virtual addr at execution */ + Elf32_Off sh_offset; /* Section file offset */ + Elf32_Word sh_size; /* Section size in bytes */ + Elf32_Word sh_link; /* Link to another section */ + Elf32_Word sh_info; /* Additional section information */ + Elf32_Word sh_addralign; /* Section alignment */ + Elf32_Word sh_entsize; /* Entry size if section holds table */ +} Elf32_Shdr; + +// sh_type values +#define SHT_NULL 0 /* Inactive section */ +#define SHT_PROGBITS 1 /* Proprietary */ +#define SHT_SYMTAB 2 /* Symbol table */ +#define SHT_STRTAB 3 /* String table */ +#define SHT_RELA 4 /* Relocation entries with addend */ +#define SHT_HASH 5 /* Symbol hash table */ +#define SHT_DYNAMIC 6 /* Info for dynamic linking */ +#define SHT_NOTE 7 /* Note section */ +#define SHT_NOBITS 8 /* Occupies no space */ +#define SHT_REL 9 /* Relocation entries without addend */ +#define SHT_SHLIB 10 /* Reserved */ +#define SHT_DYNSYM 11 /* Minimal set of dynamic linking symbols */ +#define SHT_MIPS_LIBLSIT 0x70000000 /* Info about dynamic shared object libs for MIPS*/ +#define SHT_MIPS_CONFLICT 0x70000002 /* Conflicts btw executables and shared objects for MIPS */ +#define SHT_MIPS_GPTAB 0x70000003 /* Global pointer table for MIPS*/ +#define SHT_ARM_EXIDX 0x70000001 /* Exception Index table for ARM*/ +#define SHT_ARM_PREEMPTMAP 0x70000002 /* BPABI DLL dynamic linking pre-emption map for ARM */ +#define SHT_ARM_ATTRIBUTES 0x70000003 /* Object file compatibility attributes for ARM*/ + +// sh_flags values +#define SHF_WRITE 0 /* writable section */ +#define SHF_ALLOC 2 /* section occupies memory */ +#define SHF_EXECINSTR 4 /* machine instructions */ +#define SHF_MIPS_GPREL 0x10000000 /* Must be made part of global data area for MIPS */ + +// Symbol entry (contain info about a symbol) +typedef struct { + Elf32_Word st_name; /* Symbol name (string tbl index) */ + Elf32_Addr st_value; /* Symbol value */ + Elf32_Word st_size; /* Symbol size */ + unsigned char st_info; /* Symbol type and binding */ + unsigned char st_other; /* Symbol visibility */ + Elf32_Section st_shndx; /* Section index */ +} Elf32_Sym; + +// Extract from the st_info +#define SYM_TYPE(x) ((x)&0xF) +#define SYM_BIND(x) ((x)>>4) + +// Symbol binding values from st_info +#define STB_LOCAL 0 /* Symbol not visible outside object */ +#define STB_GLOBAL 1 /* Symbol visible to all object files */ +#define STB_WEAK 2 /* Similar to STB_GLOBAL */ + +// Symbol type values from st_info +#define STT_NOTYPE 0 /* Not specified */ +#define STT_OBJECT 1 /* Data object e.g. variable */ +#define STT_FUNC 2 /* Function */ +#define STT_SECTION 3 /* Section */ +#define STT_FILE 4 /* Source file associated with object file */ + +// Special section header index values from st_shndex +#define SHN_UNDEF 0 +#define SHN_LOPROC 0xFF00 /* Extended values */ +#define SHN_ABS 0xFFF1 /* Absolute value: don't relocate */ +#define SHN_COMMON 0xFFF2 /* Common block. Not allocated yet */ +#define SHN_HIPROC 0xFF1F +#define SHN_HIRESERVE 0xFFFF + +// Relocation entry with implicit addend (info about how to relocate) +typedef struct { + Elf32_Addr r_offset; /* Address */ + Elf32_Word r_info; /* Relocation type and symbol index */ +} Elf32_Rel; + +// Relocation entry with explicit addend (info about how to relocate) +typedef struct +{ + Elf32_Addr r_offset; /* Address */ + Elf32_Word r_info; /* Relocation type and symbol index */ + Elf32_Sword r_addend; /* Addend */ +} Elf32_Rela; + +// Access macros for the relocation info +#define REL_TYPE(x) ((unsigned char) (x)) /* Extract relocation type */ +#define REL_INDEX(x) ((x)>>8) /* Extract relocation index into symbol table */ + +//MIPS relocation types +#define R_MIPS_NONE 0 +#define R_MIPS_16 1 +#define R_MIPS_32 2 +#define R_MIPS_REL32 3 +#define R_MIPS_26 4 +#define R_MIPS_HI16 5 +#define R_MIPS_LO16 6 +#define R_MIPS_GPREL16 7 +#define R_MIPS_LITERAL 8 +#define R_MIPS_GOT16 9 +#define R_MIPS_PC16 10 +#define R_MIPS_CALL16 11 +#define R_MIPS_GPREL32 12 +#define R_MIPS_GOTHI16 13 +#define R_MIPS_GOTLO16 14 +#define R_MIPS_CALLHI16 15 +#define R_MIPS_CALLLO16 16 + +// ARM relocation types +#define R_ARM_NONE 0 +#define R_ARM_ABS32 2 +#define R_ARM_THM_CALL 10 +#define R_ARM_CALL 28 +#define R_ARM_JUMP24 29 +#define R_ARM_TARGET1 38 +#define R_ARM_V4BX 40 + +// Mock function to get value of global pointer for MIPS +#define getGP() ({ \ + unsigned int __valgp; \ + __asm__ ("add %0, $gp, $0" : "=r"(__valgp) : ); \ + __valgp; \ +}) + +#endif /* BACKENDS_ELF_H */ diff --git a/backends/plugins/mips-loader.cpp b/backends/plugins/mips-loader.cpp new file mode 100644 index 0000000000..7b59a2cffa --- /dev/null +++ b/backends/plugins/mips-loader.cpp @@ -0,0 +1,351 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#if defined(DYNAMIC_MODULES) && defined(MIPS_TARGET) + +#include "mips-loader.h" + +#define __DEBUG_PLUGINS__ + +#ifdef __DEBUG_PLUGINS__ +#define DBG(x,...) printf(x, ## __VA_ARGS__) +#else +#define DBG(x,...) +#endif + +#define seterror(x,...) printf(x, ## __VA_ARGS__) + +/** + * Follow the instruction of a relocation section. + * + * @param DLFile SeekableReadStream of File + * @param offset Offset into the File + * @param size Size of relocation section + * @param relSegment Base address of relocated segment in memory (memory offset) + * + */ +bool MIPSDLObject::relocate(Common::SeekableReadStream* DLFile, unsigned long offset, unsigned long size, void *relSegment) { + Elf32_Rel *rel = NULL; // relocation entry + + // Allocate memory for relocation table + if (!(rel = (Elf32_Rel *)malloc(size))) { + seterror("Out of memory."); + return false; + } + + // Read in our relocation table + if (DLFile->seek(offset, SEEK_SET) < 0 || + DLFile->read(rel, size) != (ssize_t)size) { + seterror("Relocation table load failed."); + free(rel); + return false; + } + + // Treat each relocation entry. Loop over all of them + int cnt = size / sizeof(*rel); + + DBG("Loaded relocation table. %d entries. base address=%p\n", cnt, relSegment); + + bool seenHi16 = false; // For treating HI/LO16 commands + int firstHi16 = -1; // Mark the point of the first hi16 seen + Elf32_Addr ahl = 0; // Calculated addend + int a = 0; // Addend: taken from the target + + unsigned int *lastTarget = 0; // For processing hi16 when lo16 arrives + unsigned int relocation = 0; + int debugRelocs[10] = {0}; // For debugging + int extendedHi16 = 0; // Count extended hi16 treatments + Elf32_Addr lastHiSymVal = 0; + bool hi16InShorts = false; + +#define DEBUG_NUM 2 + + // Loop over relocation entries + for (int i = 0; i < cnt; i++) { + // Get the symbol this relocation entry is referring to + Elf32_Sym *sym = (Elf32_Sym *)(_symtab) + (REL_INDEX(rel[i].r_info)); + + // Get the target instruction in the code + unsigned int *target = (unsigned int *)((char *)relSegment + rel[i].r_offset); + + unsigned int origTarget = *target; // Save for debugging + + // Act differently based on the type of relocation + switch (REL_TYPE(rel[i].r_info)) { + + case R_MIPS_HI16: // Absolute addressing. + if (sym->st_shndx < SHN_LOPROC && // Only shift for plugin section (ie. has a real section index) + firstHi16 < 0) { // Only process first in block of HI16s + firstHi16 = i; // Keep the first Hi16 we saw + seenHi16 = true; + ahl = (*target & 0xffff) << 16; // Take lower 16 bits shifted up + + lastHiSymVal = sym->st_value; + hi16InShorts = (ShortsMan.inGeneralSegment((char *)sym->st_value)); // Fix for problem with switching btw segments + if (debugRelocs[0]++ < DEBUG_NUM) // Print only a set number + DBG("R_MIPS_HI16: i=%d, offset=%x, ahl = %x, target = %x\n", + i, rel[i].r_offset, ahl, *target); + } + break; + + case R_MIPS_LO16: // Absolute addressing. Needs a HI16 to come before it + if (sym->st_shndx < SHN_LOPROC) { // Only shift for plugin section. (ie. has a real section index) + if (!seenHi16) { // We MUST have seen HI16 first + seterror("R_MIPS_LO16 w/o preceding R_MIPS_HI16 at relocation %d!\n", i); + free(rel); + return false; + } + + // Fix: bug in gcc makes LO16s connect to wrong HI16s sometimes (shorts and regular segment) + // Note that we can check the entire shorts segment because the executable's shorts don't belong to this plugin section + // and will be screened out above + bool lo16InShorts = ShortsMan.inGeneralSegment((char *)sym->st_value); + + // Correct the bug by getting the proper value in ahl (taken from the current symbol) + if ((hi16InShorts && !lo16InShorts) || (!hi16InShorts && lo16InShorts)) { + ahl -= (lastHiSymVal & 0xffff0000); // We assume gcc meant the same offset + ahl += (sym->st_value & 0xffff0000); + } + + ahl &= 0xffff0000; // Clean lower 16 bits for repeated LO16s + a = *target & 0xffff; // Take lower 16 bits of the target + a = (a << 16) >> 16; // Sign extend them + ahl += a; // Add lower 16 bits. AHL is now complete + + // Fix: we can have LO16 access to the short segment sometimes + if (lo16InShorts) { + relocation = ahl + _shortsSegment->getOffset(); // Add in the short segment offset + } else // It's in the regular segment + relocation = ahl + (Elf32_Addr)_segment; // Add in the new offset for the segment + + if (firstHi16 >= 0) { // We haven't treated the HI16s yet so do it now + for (int j = firstHi16; j < i; j++) { + if (REL_TYPE(rel[j].r_info) != R_MIPS_HI16) continue; // Skip over non-Hi16s + + lastTarget = (unsigned int *)((char *)relSegment + rel[j].r_offset); // get hi16 target + *lastTarget &= 0xffff0000; // Clear the lower 16 bits of the last target + *lastTarget |= (relocation >> 16) & 0xffff; // Take the upper 16 bits of the relocation + if (relocation & 0x8000)(*lastTarget)++; // Subtle: we need to add 1 to the HI16 in this case + } + firstHi16 = -1; // Reset so we'll know we treated it + } else { + extendedHi16++; + } + + *target &= 0xffff0000; // Clear the lower 16 bits of current target + *target |= relocation & 0xffff; // Take the lower 16 bits of the relocation + + if (debugRelocs[1]++ < DEBUG_NUM) + DBG("R_MIPS_LO16: i=%d, offset=%x, a=%x, ahl = %x, lastTarget = %x, origt = %x, target = %x\n", + i, rel[i].r_offset, a, ahl, *lastTarget, origTarget, *target); + if (lo16InShorts && debugRelocs[2]++ < DEBUG_NUM) + DBG("R_MIPS_LO16s: i=%d, offset=%x, a=%x, ahl = %x, lastTarget = %x, origt = %x, target = %x\n", + i, rel[i].r_offset, a, ahl, *lastTarget, origTarget, *target); + } + break; + + case R_MIPS_26: // Absolute addressing (for jumps and branches only) + if (sym->st_shndx < SHN_LOPROC) { // Only relocate for main segment + a = *target & 0x03ffffff; // Get 26 bits' worth of the addend + a = (a << 6) >> 6; // Sign extend a + relocation = ((a << 2) + (Elf32_Addr)_segment) >> 2; // a already points to the target. Subtract our offset + *target &= 0xfc000000; // Clean lower 26 target bits + *target |= (relocation & 0x03ffffff); + + if (debugRelocs[3]++ < DEBUG_NUM) + DBG("R_MIPS_26: i=%d, offset=%x, symbol=%d, stinfo=%x, a=%x, origTarget=%x, target=%x\n", + i, rel[i].r_offset, REL_INDEX(rel[i].r_info), sym->st_info, a, origTarget, *target); + } else { + if (debugRelocs[4]++ < DEBUG_NUM) + DBG("R_MIPS_26: i=%d, offset=%x, symbol=%d, stinfo=%x, a=%x, origTarget=%x, target=%x\n", + i, rel[i].r_offset, REL_INDEX(rel[i].r_info), sym->st_info, a, origTarget, *target); + } + break; + + case R_MIPS_GPREL16: // GP Relative addressing + if (_shortsSegment->getOffset() != 0 && // Only relocate if we shift the shorts section + ShortsMan.inGeneralSegment((char *)sym->st_value)) { // Only relocate things in the plugin hole + a = *target & 0xffff; // Get 16 bits' worth of the addend + a = (a << 16) >> 16; // Sign extend it + + relocation = a + _shortsSegment->getOffset(); + + *target &= 0xffff0000; // Clear the lower 16 bits of the target + *target |= relocation & 0xffff; + + if (debugRelocs[5]++ < DEBUG_NUM) + DBG("R_MIPS_GPREL16: i=%d, a=%x, gpVal=%x, origTarget=%x, target=%x, offset=%x\n", + i, a, _gpVal, origTarget, *target, _shortsSegment->getOffset()); + } + + break; + + case R_MIPS_32: // Absolute addressing + if (sym->st_shndx < SHN_LOPROC) { // Only shift for plugin section. + a = *target; // Get full 32 bits of addend + + if (ShortsMan.inGeneralSegment((char *)sym->st_value)) // Check if we're in the shorts segment + relocation = a + _shortsSegment->getOffset(); // Shift by shorts offset + else // We're in the main section + relocation = a + (Elf32_Addr)_segment; // Shift by main offset + *target = relocation; + + if (debugRelocs[6]++ < DEBUG_NUM) + DBG("R_MIPS_32: i=%d, a=%x, origTarget=%x, target=%x\n", i, a, origTarget, *target); + } + break; + + default: + seterror("Unknown relocation type %x at relocation %d.\n", REL_TYPE(rel[i].r_info), i); + free(rel); + return false; + } + } + + DBG("Done with relocation. extendedHi16=%d\n\n", extendedHi16); + + free(rel); + return true; +} + +bool MIPSDLObject::relocateRels(Common::SeekableReadStream* DLFile, Elf32_Ehdr *ehdr, Elf32_Shdr *shdr) { + + // Loop over sections, finding relocation sections + for (int i = 0; i < ehdr->e_shnum; i++) { + + Elf32_Shdr *curShdr = &(shdr[i]); + //Elf32_Shdr *linkShdr = &(shdr[curShdr->sh_info]); + + if (curShdr->sh_type == SHT_REL && // Check for a relocation section + curShdr->sh_entsize == sizeof(Elf32_Rel) && // Check for proper relocation size + (int)curShdr->sh_link == _symtab_sect && // Check that the sh_link connects to our symbol table + curShdr->sh_info < ehdr->e_shnum && // Check that the relocated section exists + (shdr[curShdr->sh_info].sh_flags & SHF_ALLOC)) { // Check if relocated section resides in memory + if (!ShortsMan.inGeneralSegment((char *)shdr[curShdr->sh_info].sh_addr)) { // regular segment + if (!relocate(DLFile, curShdr->sh_offset, curShdr->sh_size, _segment)) { + return false; + } + } else { // In Shorts segment + if (!relocate(DLFile, curShdr->sh_offset, curShdr->sh_size, (void *)_shortsSegment->getOffset())) { + return false; + } + } + + } + } + + return true; +} + +void MIPSDLObject::relocateSymbols(Elf32_Addr offset) { + + int mainCount = 0; + int shortsCount= 0; + + // Loop over symbols, add relocation offset + Elf32_Sym *s = (Elf32_Sym *)_symtab; + for (int c = _symbol_cnt; c--; s++) { + + // Make sure we don't relocate special valued symbols + if (s->st_shndx < SHN_LOPROC) { + if (!ShortsMan.inGeneralSegment((char *)s->st_value)) { + mainCount++; + s->st_value += offset; + if (s->st_value < (Elf32_Addr)_segment || s->st_value > (Elf32_Addr)_segment + _segmentSize) + seterror("Symbol out of bounds! st_value = %x\n", s->st_value); + } else { // shorts section + shortsCount++; + s->st_value += _shortsSegment->getOffset(); + if (!_shortsSegment->inSegment((char *)s->st_value)) + seterror("Symbol out of bounds! st_value = %x\n", s->st_value); + } + + } + } +} + +bool MIPSDLObject::loadSegment(Common::SeekableReadStream* DLFile, Elf32_Phdr *phdr) { + + char *baseAddress = 0; + + // We need to take account of non-allocated segment for shorts + if (phdr->p_flags & PF_X) { // This is a relocated segment + + // Attempt to allocate memory for segment + int extra = phdr->p_vaddr % phdr->p_align; // Get extra length TODO: check logic here + DBG("extra mem is %x\n", extra); + + if (phdr->p_align < 0x10000) phdr->p_align = 0x10000; // Fix for wrong alignment on e.g. AGI + + if (!(_segment = (char *)memalign(phdr->p_align, phdr->p_memsz + extra))) { + seterror("Out of memory.\n"); + return false; + } + DBG("allocated segment @ %p\n", _segment); + + // Get offset to load segment into + baseAddress = (char *)_segment + phdr->p_vaddr; + _segmentSize = phdr->p_memsz + extra; + } else { // This is a shorts section. + _shortsSegment = ShortsMan.newSegment(phdr->p_memsz, (char *)phdr->p_vaddr); + + baseAddress = _shortsSegment->getStart(); + DBG("shorts segment @ %p to %p. Segment wants to be at %x. Offset=%x\n", + _shortsSegment->getStart(), _shortsSegment->getEnd(), phdr->p_vaddr, _shortsSegment->getOffset()); + } + + // Set bss segment to 0 if necessary (assumes bss is at the end) + if (phdr->p_memsz > phdr->p_filesz) { + DBG("Setting %p to %p to 0 for bss\n", baseAddress + phdr->p_filesz, baseAddress + phdr->p_memsz); + memset(baseAddress + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz); + } + + DBG("Reading the segment into memory\n"); + + // Read the segment into memory + if (DLFile->seek(phdr->p_offset, SEEK_SET) < 0 || + DLFile->read(baseAddress, phdr->p_filesz) != (ssize_t)phdr->p_filesz) { + seterror("Segment load failed."); + return false; + } + + DBG("Segment has been read into memory\n"); + + return true; +} + +// Unload all objects from memory +void MIPSDLObject::unload() { + discard_symtab(); + free(_segment); + _segment = NULL; + + if (_shortsSegment) { + ShortsMan.deleteSegment(_shortsSegment); + _shortsSegment = NULL; + } +} + +#endif /* defined(DYNAMIC_MODULES) && defined(MIPS_TARGET) */ diff --git a/backends/plugins/mips-loader.h b/backends/plugins/mips-loader.h new file mode 100644 index 0000000000..eb22e368f4 --- /dev/null +++ b/backends/plugins/mips-loader.h @@ -0,0 +1,46 @@ + +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "elf-loader.h" +#include "shorts-segment-manager.h" + +class MIPSDLObject : public DLObject { +protected: + ShortSegmentManager::Segment *_shortsSegment; // For assigning shorts ranges + unsigned int _gpVal; // Value of Global Pointer + + bool relocate(Common::SeekableReadStream* DLFile, unsigned long offset, unsigned long size, void *relSegment); + bool relocateRels(Common::SeekableReadStream* DLFile, Elf32_Ehdr *ehdr, Elf32_Shdr *shdr); + void relocateSymbols(Elf32_Addr offset); + bool loadSegment(Common::SeekableReadStream* DLFile, Elf32_Phdr *phdr); + void unload(); + +public: + MIPSDLObject() : DLObject() { + _shortsSegment = NULL; + _gpVal = 0; + } +}; diff --git a/backends/platform/psp/plugin.syms b/backends/plugins/plugin.syms index 24ee1a19dc..24ee1a19dc 100644 --- a/backends/platform/psp/plugin.syms +++ b/backends/plugins/plugin.syms diff --git a/backends/plugins/ps2/main_prog.ld b/backends/plugins/ps2/main_prog.ld new file mode 100644 index 0000000000..9dba69c50e --- /dev/null +++ b/backends/plugins/ps2/main_prog.ld @@ -0,0 +1,99 @@ +ENTRY(_start); + +SECTIONS { + .text 0x00100000: { + _ftext = . ; + *(.text) + *(.text.*) + *(.gnu.linkonce.t*) + KEEP(*(.init)) + KEEP(*(.fini)) + QUAD(0) + } + + PROVIDE(_etext = .); + PROVIDE(etext = .); + + .reginfo : { *(.reginfo) } + + /* Global/static constructors and deconstructors. */ + .ctors ALIGN(16): { + KEEP(*crtbegin*.o(.ctors)) + KEEP(*(EXCLUDE_FILE(*crtend*.o) .ctors)) + KEEP(*(SORT(.ctors.*))) + KEEP(*(.ctors)) + } + .dtors ALIGN(16): { + KEEP(*crtbegin*.o(.dtors)) + KEEP(*(EXCLUDE_FILE(*crtend*.o) .dtors)) + KEEP(*(SORT(.dtors.*))) + KEEP(*(.dtors)) + } + + /* Static data. */ + .rodata ALIGN(128): { + *(.rodata) + *(.rodata.*) + *(.gnu.linkonce.r*) + } + + .data ALIGN(128): { + _fdata = . ; + *(.data) + *(.data.*) + *(.gnu.linkonce.d*) + SORT(CONSTRUCTORS) + } + + .rdata ALIGN(128): { *(.rdata) } + .gcc_except_table ALIGN(128): { *(.gcc_except_table) } + + _gp = ALIGN(128) + 0x7ff0; + .lit4 ALIGN(128): { *(.lit4) } + .lit8 ALIGN(128): { *(.lit8) } + + .sdata ALIGN(128): { + *(.sdata) + *(.sdata.*) + *(.gnu.linkonce.s*) + } + + _edata = .; + PROVIDE(edata = .); + + /* Uninitialized data. */ + .sbss ALIGN(128) : { + _fbss = . ; + *(.sbss) + *(.sbss.*) + *(.gnu.linkonce.sb*) + *(.scommon) + } + + /*This "plugin hole" is so the plugins can all have global small data + in the same place.*/ + __plugin_hole_start = .; + . = _gp + 0x7ff0; + __plugin_hole_end = .; + + COMMON : + { + *(COMMON) + } + . = ALIGN(128); + + .bss ALIGN(128) : { + *(.bss) + *(.bss.*) + *(.gnu.linkonce.b*) + } + _end_bss = .; + + _end = . ; + PROVIDE(end = .); + + /* Symbols needed by crt0.s. */ + PROVIDE(_heap_size = -1); + PROVIDE(_stack = -1); + PROVIDE(_stack_size = 128 * 1024); +} diff --git a/backends/plugins/ps2/plugin.ld b/backends/plugins/ps2/plugin.ld new file mode 100644 index 0000000000..9879413b98 --- /dev/null +++ b/backends/plugins/ps2/plugin.ld @@ -0,0 +1,94 @@ +/* PHDRS specifies ELF Program Headers (or segments) to the plugin linker */ +PHDRS { + plugin PT_LOAD ; /* Specifies that the plugin segment should be loaded from file */ + shorts PT_LOAD ; /* Specifies that the shorts segment should be loaded from file */ +} +SECTIONS { + .text 0: { + _ftext = . ; + *(.text) + *(.text.*) + *(.gnu.linkonce.t*) + KEEP(*(.init)) + KEEP(*(.fini)) + QUAD(0) + } : plugin /*The ": plugin" tells the linker to assign this and + the following sections to the "plugin" segment*/ + PROVIDE(_etext = .); + PROVIDE(etext = .); + + .reginfo : { *(.reginfo) } + + /* Global/static constructors and deconstructors. */ + .ctors ALIGN(16): { + ___plugin_ctors = .; + KEEP(*(SORT(.ctors.*))) + KEEP(*(.ctors)) + ___plugin_ctors_end = .; + } + .dtors ALIGN(16): { + ___plugin_dtors = .; + KEEP(*(SORT(.dtors.*))) + KEEP(*(.dtors)) + ___plugin_dtors_end = .; + } + + /* Static data. */ + .rodata ALIGN(128): { + *(.rodata) + *(.rodata.*) + *(.gnu.linkonce.r*) + } + + .data ALIGN(128): { + _fdata = . ; + *(.data) + *(.data.*) + *(.gnu.linkonce.d*) + SORT(CONSTRUCTORS) + } + + .rdata ALIGN(128): { *(.rdata) } + .gcc_except_table ALIGN(128): { *(.gcc_except_table) } + + .bss ALIGN(128) : { + *(.bss) + *(.bss.*) + *(.gnu.linkonce.b*) + *(COMMON) + } + _end_bss = .; + + _end = . ; + PROVIDE(end = .); + + /* Symbols needed by crt0.s. */ + PROVIDE(_heap_size = -1); + PROVIDE(_stack = -1); + PROVIDE(_stack_size = 128 * 1024); + + /*We assign the output location counter to the plugin hole made + in main_prog.ld, then assign the small data sections to the shorts segment*/ + . = __plugin_hole_start; + .lit4 ALIGN(128): { *(.lit4) } : shorts + .lit8 ALIGN(128): { *(.lit8) } + + .sdata ALIGN(128): { + *(.sdata) + *(.sdata.*) + *(.gnu.linkonce.s*) + } + + _edata = .; + PROVIDE(edata = .); + + /* Uninitialized data. */ + .sbss ALIGN(128) : { + _fbss = . ; + *(.sbss) + *(.sbss.*) + *(.gnu.linkonce.sb*) + *(.scommon) + } + +} diff --git a/backends/plugins/ps2/ps2-provider.cpp b/backends/plugins/ps2/ps2-provider.cpp new file mode 100644 index 0000000000..096c6d4050 --- /dev/null +++ b/backends/plugins/ps2/ps2-provider.cpp @@ -0,0 +1,44 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#if defined(DYNAMIC_MODULES) && defined(__PLAYSTATION2__) + +#include "backends/plugins/mips-loader.h" +#include "backends/plugins/elf-provider.h" +#include "backends/plugins/ps2/ps2-provider.h" + + +class PS2Plugin : public ELFPlugin { +public: + PS2Plugin(const Common::String &filename) : ELFPlugin(filename) {} + + DLObject *makeDLObject() { return new MIPSDLObject(); } +}; + +Plugin* PS2PluginProvider::createPlugin(const Common::FSNode &node) const { + return new PS2Plugin(node.getPath()); +} + +#endif // defined(DYNAMIC_MODULES) && defined(ELF_LOADER_TARGET) diff --git a/backends/plugins/ps2/ps2-provider.h b/backends/plugins/ps2/ps2-provider.h new file mode 100644 index 0000000000..28a2321c29 --- /dev/null +++ b/backends/plugins/ps2/ps2-provider.h @@ -0,0 +1,31 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "backends/plugins/elf-provider.h" + +class PS2PluginProvider : public ELFPluginProvider { + Plugin* createPlugin(const Common::FSNode &node) const; +}; + diff --git a/backends/platform/psp/plugin.ld b/backends/plugins/psp/plugin.ld index db4df45264..db4df45264 100644 --- a/backends/platform/psp/plugin.ld +++ b/backends/plugins/psp/plugin.ld diff --git a/backends/plugins/psp/psp-provider.cpp b/backends/plugins/psp/psp-provider.cpp index 5760424cbf..99aa33c123 100644 --- a/backends/plugins/psp/psp-provider.cpp +++ b/backends/plugins/psp/psp-provider.cpp @@ -25,86 +25,20 @@ #if defined(DYNAMIC_MODULES) && defined(__PSP__) +#include "backends/plugins/mips-loader.h" +#include "backends/plugins/elf-provider.h" #include "backends/plugins/psp/psp-provider.h" -#include "backends/plugins/dynamic-plugin.h" -#include "common/fs.h" -#include "backends/platform/psp/psploader.h" - -#include "backends/platform/psp/trace.h" - - -class PSPPlugin : public DynamicPlugin { -protected: - void *_dlHandle; - Common::String _filename; - - virtual VoidFunc findSymbol(const char *symbol) { - void *func = dlsym(_dlHandle, symbol); - if (!func) - warning("Failed loading symbol '%s' from plugin '%s' (%s)", symbol, _filename.c_str(), dlerror()); - - // FIXME HACK: This is a HACK to circumvent a clash between the ISO C++ - // standard and POSIX: ISO C++ disallows casting between function pointers - // and data pointers, but dlsym always returns a void pointer. For details, - // see e.g. <http://www.trilithium.com/johan/2004/12/problem-with-dlsym/>. - assert(sizeof(VoidFunc) == sizeof(func)); - VoidFunc tmp; - memcpy(&tmp, &func, sizeof(VoidFunc)); - return tmp; - } +class PSPPlugin : public ELFPlugin { public: - PSPPlugin(const Common::String &filename) - : _dlHandle(0), _filename(filename) {} - - ~PSPPlugin() { - if (_dlHandle) unloadPlugin(); - } - - bool loadPlugin() { - assert(!_dlHandle); - _dlHandle = dlopen(_filename.c_str(), RTLD_LAZY); - - if (!_dlHandle) { - warning("Failed loading plugin '%s' (%s)", _filename.c_str(), dlerror()); - return false; - } + PSPPlugin(const Common::String &filename) : ELFPlugin(filename) {} - bool ret = DynamicPlugin::loadPlugin(); - - if (ret) - dlforgetsyms(_dlHandle); - - return ret; - } - - void unloadPlugin() { - DynamicPlugin::unloadPlugin(); - if (_dlHandle) { - if (dlclose(_dlHandle) != 0) - warning("Failed unloading plugin '%s' (%s)", _filename.c_str(), dlerror()); - _dlHandle = 0; - } - } + DLObject *makeDLObject() { return new MIPSDLObject(); } }; - Plugin* PSPPluginProvider::createPlugin(const Common::FSNode &node) const { return new PSPPlugin(node.getPath()); } -bool PSPPluginProvider::isPluginFilename(const Common::FSNode &node) const { - // Check the plugin suffix - Common::String filename = node.getName(); - PSP_DEBUG_PRINT("Testing name %s", filename.c_str()); - if (!filename.hasSuffix(".PLG") && !filename.hasSuffix(".plg")) { - PSP_DEBUG_PRINT(" fail.\n"); - return false; - } - - PSP_DEBUG_PRINT(" success!\n"); - return true; -} - #endif // defined(DYNAMIC_MODULES) && defined(__PSP__) diff --git a/backends/plugins/psp/psp-provider.h b/backends/plugins/psp/psp-provider.h index d6c44c5a85..c4debc7997 100644 --- a/backends/plugins/psp/psp-provider.h +++ b/backends/plugins/psp/psp-provider.h @@ -26,16 +26,12 @@ #ifndef BACKENDS_PLUGINS_PSP_PSP_PROVIDER_H #define BACKENDS_PLUGINS_PSP_PSP_PROVIDER_H -#include "base/plugins.h" +#include "backends/plugins/elf-provider.h" #if defined(DYNAMIC_MODULES) && defined(__PSP__) -class PSPPluginProvider : public FilePluginProvider { -protected: +class PSPPluginProvider : public ELFPluginProvider { Plugin* createPlugin(const Common::FSNode &node) const; - - bool isPluginFilename(const Common::FSNode &node) const; - }; #endif // defined(DYNAMIC_MODULES) && defined(__PSP__) diff --git a/backends/plugins/shorts-segment-manager.cpp b/backends/plugins/shorts-segment-manager.cpp new file mode 100644 index 0000000000..2376759919 --- /dev/null +++ b/backends/plugins/shorts-segment-manager.cpp @@ -0,0 +1,89 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#if defined(DYNAMIC_MODULES) && defined(MIPS_TARGET) + +#include "shorts-segment-manager.h" + +extern char __plugin_hole_start; // Indicates start of hole in program file for shorts +extern char __plugin_hole_end; // Indicates end of hole in program file +extern char _gp[]; // Value of gp register + +#ifdef DEBUG_PLUGINS +#define DBG(x,...) printf(x, ## __VA_ARGS__) +#else +#define DBG(x,...) +#endif + +#define seterror(x,...) printf(x, ## __VA_ARGS__) + +DECLARE_SINGLETON(ShortSegmentManager); // For singleton + +ShortSegmentManager::ShortSegmentManager() { + _shortsStart = &__plugin_hole_start ; //shorts segment begins at the plugin hole we made when linking + _shortsEnd = &__plugin_hole_end; //and ends at the end of that hole. +} + +ShortSegmentManager::Segment *ShortSegmentManager::newSegment(int size, char *origAddr) { + char *lastAddress = origAddr; + Common::List<Segment *>::iterator i; + + // Find a block that fits, starting from the beginning + for (i = _list.begin(); i != _list.end(); ++i) { + char *currAddress = (*i)->getStart(); + + if ((int)(currAddress - lastAddress) >= size) break; + + lastAddress = (*i)->getEnd(); + } + + if ((Elf32_Addr)lastAddress & 3) + lastAddress += 4 - ((Elf32_Addr)lastAddress & 3); // Round up to multiple of 4 + + if (lastAddress + size > _shortsEnd) { + seterror("Error. No space in shorts segment for %x bytes. Last address is %p, max address is %p.\n", + size, lastAddress, _shortsEnd); + return NULL; + } + + Segment *seg = new Segment(lastAddress, size, origAddr); // Create a new segment + + if (lastAddress + size > _highestAddress) _highestAddress = lastAddress + size; // Keep track of maximum + + _list.insert(i, seg); + + DBG("Shorts segment size %x allocated. End = %p. Remaining space = %x. Highest so far is %p.\n", + size, lastAddress + size, _shortsEnd - _list.back()->getEnd(), _highestAddress); + + return seg; +} + +void ShortSegmentManager::deleteSegment(ShortSegmentManager::Segment *seg) { + DBG("Deleting shorts segment from %p to %p.\n\n", seg->getStart(), seg->getEnd()); + _list.remove(seg); + delete seg; +} + +#endif /* DYNAMIC_MODULES && MIPS_TARGET */ diff --git a/backends/platform/psp/psploader.h b/backends/plugins/shorts-segment-manager.h index 13dcf6ef98..54a13d88e1 100644 --- a/backends/platform/psp/psploader.h +++ b/backends/plugins/shorts-segment-manager.h @@ -23,17 +23,21 @@ * */ -#ifndef PSPLOADER_H -#define PSPLOADER_H +#ifndef SHORTS_SEGMENT_MANAGER_H +#define SHORTS_SEGMENT_MANAGER_H -#include "elf32.h" -#include "common/list.h" #include "common/singleton.h" +#include "common/list.h" +#include "elf32.h" -#define MAXDLERRLEN 80 - -#define ShortsMan ShortSegmentManager::instance() +#define ShortsMan ShortSegmentManager::instance() +/** + * Manages the segments of small data put in the gp-relative area for MIPS processors, + * and lets these segments be handled differently in the ELF loader. + * Since there's no true dynamic linker to change the GP register between plugins and the main engine, + * custom linker scripts ensure the GP-area is in the same place for both. + */ class ShortSegmentManager : public Common::Singleton<ShortSegmentManager> { private: char *_shortsStart; @@ -43,6 +47,8 @@ public: char *getShortsStart() { return _shortsStart; } + + // Returns whether or not an absolute address is in the GP-relative section. bool inGeneralSegment(char *addr) { return ((char *)addr >= _shortsStart && (char *)addr < _shortsEnd); } @@ -80,58 +86,4 @@ private: char *_highestAddress; }; - - - -class DLObject { -protected: - char *_errbuf; /* For error messages, at least MAXDLERRLEN in size */ - - ShortSegmentManager::Segment *_shortsSegment; // For assigning shorts ranges - void *_segment, *_symtab; - char *_strtab; - int _symbol_cnt; - int _symtab_sect; - void *_dtors_start, *_dtors_end; - - unsigned int _gpVal; // Value of Global Pointer - int _segmentSize; - - void seterror(const char *fmt, ...); - void unload(); - bool relocate(int fd, unsigned long offset, unsigned long size, void *); - bool load(int fd); - - bool readElfHeader(int fd, Elf32_Ehdr *ehdr); - bool readProgramHeaders(int fd, Elf32_Ehdr *ehdr, Elf32_Phdr *phdr, int num); - bool loadSegment(int fd, Elf32_Phdr *phdr); - Elf32_Shdr *loadSectionHeaders(int fd, Elf32_Ehdr *ehdr); - int loadSymbolTable(int fd, Elf32_Ehdr *ehdr, Elf32_Shdr *shdr); - bool loadStringTable(int fd, Elf32_Shdr *shdr); - void relocateSymbols(Elf32_Addr offset, Elf32_Addr shortsOffset); - bool relocateRels(int fd, Elf32_Ehdr *ehdr, Elf32_Shdr *shdr); - -public: - bool open(const char *path); - bool close(); - void *symbol(const char *name); - void discard_symtab(); - - DLObject(char *errbuf = NULL) : _errbuf(_errbuf), _shortsSegment(NULL), _segment(NULL), _symtab(NULL), - _strtab(NULL), _symbol_cnt(0), _symtab_sect(-1), _dtors_start(NULL), _dtors_end(NULL), _gpVal(0) , - _segmentSize(0) {} -}; - - - -#define RTLD_LAZY 0 - -extern "C" { - void *dlopen(const char *filename, int flags); - int dlclose(void *handle); - void *dlsym(void *handle, const char *symbol); - const char *dlerror(); - void dlforgetsyms(void *handle); -} - -#endif /* PSPLOADER_H */ +#endif /* SHORTS_SEGMENT_MANAGER_H */ diff --git a/backends/saves/default/default-saves.cpp b/backends/saves/default/default-saves.cpp index 1ab898d2d6..55aa469309 100644 --- a/backends/saves/default/default-saves.cpp +++ b/backends/saves/default/default-saves.cpp @@ -131,11 +131,11 @@ bool DefaultSaveFileManager::removeSavefile(const Common::String &filename) { // There is a nicely portable workaround, too: Make this method overloadable. if (remove(file.getPath().c_str()) != 0) { #ifndef _WIN32_WCE - if (errno == EACCES) + if (errno == EACCES) setError(Common::kWritePermissionDenied, "Search or write permission denied: "+file.getName()); if (errno == ENOENT) - setError(Common::kPathDoesNotExist, "removeSavefile: '"+file.getName()+"' does not exist or path is invalid"); + setError(Common::kPathDoesNotExist, "removeSavefile: '"+file.getName()+"' does not exist or path is invalid"); #endif return false; } else { diff --git a/base/main.cpp b/base/main.cpp index e651456ace..8564d64c26 100644 --- a/base/main.cpp +++ b/base/main.cpp @@ -105,7 +105,12 @@ static const EnginePlugin *detectPlugin() { // Query the plugins and find one that will handle the specified gameid printf("User picked target '%s' (gameid '%s')...\n", ConfMan.getActiveDomainName().c_str(), gameid.c_str()); printf("%s", " Looking for a plugin supporting this gameid... "); - GameDescriptor game = EngineMan.findGame(gameid, &plugin); + +#if defined(NEW_PLUGIN_DESIGN_FIRST_REFINEMENT) && defined(DYNAMIC_MODULES) + GameDescriptor game = EngineMan.findGameOnePlugAtATime(gameid, &plugin); +#else + GameDescriptor game = EngineMan.findGame(gameid, &plugin); +#endif if (plugin == 0) { printf("failed\n"); @@ -339,8 +344,12 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) { settings.erase("debugflags"); } - // Load the plugins. - PluginManager::instance().loadPlugins(); +#if defined(NEW_PLUGIN_DESIGN_FIRST_REFINEMENT) && defined(DYNAMIC_MODULES) //note: I'm going to refactor this name later :P + // Don't load the plugins initially in this case. +#else + // Load the plugins. + PluginManager::instance().loadPlugins(); +#endif // If we received an invalid music parameter via command line we check this here. // We can't check this before loading the music plugins. diff --git a/base/plugins.cpp b/base/plugins.cpp index 5d0be11065..61cc747a41 100644 --- a/base/plugins.cpp +++ b/base/plugins.cpp @@ -302,6 +302,31 @@ void PluginManager::addPluginProvider(PluginProvider *pp) { _providers.push_back(pp); } +bool PluginManager::loadFirstPlugin() { //TODO: only deal with engine plugins here, and have a separate "loadNonEnginePlugins" function. + unloadPluginsExcept(PLUGIN_TYPE_ENGINE, NULL); + PluginList plugs; + for (ProviderList::iterator pp = _providers.begin(); + pp != _providers.end(); + ++pp) { + PluginList pl((*pp)->getPlugins()); + for (PluginList::iterator p = pl.begin(); p != pl.end(); ++p) { + plugs.push_back(*p); + } + } + _pluginsEnd = plugs.end(); + _currentPlugin = plugs.begin(); + if (plugs.empty()) return false; //return false if there are no plugins to load. + return tryLoadPlugin(*_currentPlugin); +} + +bool PluginManager::loadNextPlugin() { + // To ensure only one engine plugin is loaded at a time, we unload all engine plugins before loading a new one. + unloadPluginsExcept(PLUGIN_TYPE_ENGINE, NULL); + ++_currentPlugin; + if (_currentPlugin == _pluginsEnd) return false; //return false if already reached the end of list of plugins. + return tryLoadPlugin(*_currentPlugin); +} + void PluginManager::loadPlugins() { for (ProviderList::iterator pp = _providers.begin(); pp != _providers.end(); @@ -309,7 +334,6 @@ void PluginManager::loadPlugins() { PluginList pl((*pp)->getPlugins()); Common::for_each(pl.begin(), pl.end(), Common::bind1st(Common::mem_fun(&PluginManager::tryLoadPlugin), this)); } - } void PluginManager::unloadPlugins() { @@ -340,7 +364,7 @@ bool PluginManager::tryLoadPlugin(Plugin *plugin) { // The plugin is valid, see if it provides the same module as an // already loaded one and should replace it. bool found = false; - + printf("Plugin loaded is %s\n", plugin->getName()); PluginList::iterator pl = _plugins[plugin->getType()].begin(); while (!found && pl != _plugins[plugin->getType()].end()) { if (!strcmp(plugin->getName(), (*pl)->getName())) { @@ -366,13 +390,24 @@ bool PluginManager::tryLoadPlugin(Plugin *plugin) { } } - // Engine plugins #include "engines/metaengine.h" DECLARE_SINGLETON(EngineManager) +GameDescriptor EngineManager::findGameOnePlugAtATime(const Common::String &gameName, const EnginePlugin **plugin) const { + GameDescriptor result; + PluginManager::instance().loadFirstPlugin(); + do { + result = findGame(gameName, plugin); + if (!result.gameid().empty()) { + break; + } + } while (PluginManager::instance().loadNextPlugin()); + return result; +} + GameDescriptor EngineManager::findGame(const Common::String &gameName, const EnginePlugin **plugin) const { // Find the GameDescriptor for this target const EnginePlugin::List &plugins = getPlugins(); @@ -381,30 +416,36 @@ GameDescriptor EngineManager::findGame(const Common::String &gameName, const Eng if (plugin) *plugin = 0; - EnginePlugin::List::const_iterator iter = plugins.begin(); - for (iter = plugins.begin(); iter != plugins.end(); ++iter) { - result = (**iter)->findGame(gameName.c_str()); - if (!result.gameid().empty()) { - if (plugin) - *plugin = *iter; - break; + EnginePlugin::List::const_iterator iter; + + for (iter = plugins.begin(); iter != plugins.end(); ++iter) { + result = (**iter)->findGame(gameName.c_str()); + if (!result.gameid().empty()) { + if (plugin) + *plugin = *iter; + return result; + } } - } return result; } GameList EngineManager::detectGames(const Common::FSList &fslist) const { GameList candidates; - - const EnginePlugin::List &plugins = getPlugins(); - - // Iterate over all known games and for each check if it might be - // the game in the presented directory. + EnginePlugin::List plugins; EnginePlugin::List::const_iterator iter; - for (iter = plugins.begin(); iter != plugins.end(); ++iter) { - candidates.push_back((**iter)->detectGames(fslist)); - } - +#if defined(NEW_PLUGIN_DESIGN_FIRST_REFINEMENT) && defined(DYNAMIC_MODULES) + PluginManager::instance().loadFirstPlugin(); + do { +#endif + plugins = getPlugins(); + // Iterate over all known games and for each check if it might be + // the game in the presented directory. + for (iter = plugins.begin(); iter != plugins.end(); ++iter) { + candidates.push_back((**iter)->detectGames(fslist)); + } +#if defined(NEW_PLUGIN_DESIGN_FIRST_REFINEMENT) && defined(DYNAMIC_MODULES) + } while (PluginManager::instance().loadNextPlugin()); +#endif return candidates; } diff --git a/base/plugins.h b/base/plugins.h index a4c7f114f9..975c815783 100644 --- a/base/plugins.h +++ b/base/plugins.h @@ -275,9 +275,11 @@ class PluginManager : public Common::Singleton<PluginManager> { private: PluginList _plugins[PLUGIN_TYPE_MAX]; ProviderList _providers; - + PluginList::iterator _currentPlugin; + PluginList::iterator _pluginsEnd; + bool tryLoadPlugin(Plugin *plugin); - + friend class Common::Singleton<SingletonBaseType>; PluginManager(); @@ -286,6 +288,9 @@ public: void addPluginProvider(PluginProvider *pp); + bool loadFirstPlugin(); + bool loadNextPlugin(); + void loadPlugins(); void unloadPlugins(); void unloadPluginsExcept(PluginType type, const Plugin *plugin); diff --git a/engines/drascula/animation.cpp b/engines/drascula/animation.cpp index d6a3bafd9f..1d6f77b8eb 100644 --- a/engines/drascula/animation.cpp +++ b/engines/drascula/animation.cpp @@ -1695,7 +1695,7 @@ void DrasculaEngine::animation_9_6() { // We set the room number to -1 for the same purpose. // Also check animation_2_1(), where the same hack was used // by the original - roomNumber = -2; + roomNumber = -1; loadPic("nota2.alg", bgSurface, HALF_PAL); black(); trackProtagonist = 1; diff --git a/engines/drascula/drascula.cpp b/engines/drascula/drascula.cpp index 7e9f68a355..b1f16fa505 100644 --- a/engines/drascula/drascula.cpp +++ b/engines/drascula/drascula.cpp @@ -88,7 +88,6 @@ DrasculaEngine::DrasculaEngine(OSystem *syst, const DrasculaGameDescription *gam _textverbs = 0; _textmisc = 0; _textd1 = 0; - _talkSequences = 0; _color = 0; blinking = 0; diff --git a/engines/drascula/objects.cpp b/engines/drascula/objects.cpp index 73aea7b7f2..08c1a68a55 100644 --- a/engines/drascula/objects.cpp +++ b/engines/drascula/objects.cpp @@ -92,8 +92,7 @@ void DrasculaEngine::gotoObject(int pointX, int pointY) { updateRoom(); updateScreen(); - // roomNumber -2 is end credits. Do not show cursor there - if (cursorVisible && roomNumber != -2) + if (cursorVisible) showCursor(); } diff --git a/engines/m4/compression.h b/engines/m4/compression.h index 00e3d1f927..74fed357ff 100644 --- a/engines/m4/compression.h +++ b/engines/m4/compression.h @@ -66,8 +66,8 @@ public: class FabDecompressor { private: - int _bitsLeft; - uint32 _bitBuffer; + int _bitsLeft; + uint32 _bitBuffer; const byte *_srcData, *_srcP; int _srcSize; diff --git a/engines/m4/console.cpp b/engines/m4/console.cpp index 71c70e3e1b..9a92cb04d1 100644 --- a/engines/m4/console.cpp +++ b/engines/m4/console.cpp @@ -47,6 +47,7 @@ Console::Console(MadsM4Engine *vm) : GUI::Debugger() { DCmd_Register("start_conv", WRAP_METHOD(Console, cmdStartConversation)); DCmd_Register("textview", WRAP_METHOD(Console, cmdShowTextview)); DCmd_Register("animview", WRAP_METHOD(Console, cmdShowAnimview)); + DCmd_Register("anim", WRAP_METHOD(Console, cmdPlayAnimation)); } Console::~Console() { @@ -246,6 +247,33 @@ bool Console::cmdShowAnimview(int argc, const char **argv) { return false; } +bool Console::cmdPlayAnimation(int argc, const char **argv) { + View *view = _vm->_viewManager->getView(VIEWID_SCENE); + if (view == NULL) { + DebugPrintf("The scene view isn't currently active\n"); + } else if (argc != 2 && argc != 3) { + DebugPrintf("Usage: %s <anim resource (*.aa)> <fullscreen>\n", argv[0]); + DebugPrintf("If fullscreen is 1, the screen palette is replaced with the palette of the animation\n"); + } else { + char resourceName[20]; + strncpy(resourceName, argv[1], 15); + resourceName[15] = '\0'; + if (!strchr(resourceName, '.')) + strcat(resourceName, ".AA"); + + _vm->_viewManager->moveToFront(view); + if (argc == 3 && atoi(argv[2]) == 1) + _vm->_animation->loadFullScreen(resourceName); + else + _vm->_animation->load(resourceName); + _vm->_animation->start(); + view->restore(0, 0, view->width(), view->height()); + return false; + } + + return true; +} + /*--------------------------------------------------------------------------*/ MadsConsole::MadsConsole(MadsEngine *vm): Console(vm) { @@ -254,7 +282,6 @@ MadsConsole::MadsConsole(MadsEngine *vm): Console(vm) { DCmd_Register("object", WRAP_METHOD(MadsConsole, cmdObject)); DCmd_Register("message", WRAP_METHOD(MadsConsole, cmdMessage)); DCmd_Register("scene_info", WRAP_METHOD(MadsConsole, cmdSceneInfo)); - DCmd_Register("anim", WRAP_METHOD(MadsConsole, cmdPlayAnimation)); } bool MadsConsole::cmdObject(int argc, const char **argv) { @@ -359,33 +386,6 @@ bool MadsConsole::cmdSceneInfo(int argc, const char **argv) { return true; } -bool MadsConsole::cmdPlayAnimation(int argc, const char **argv) { - View *view = _vm->_viewManager->getView(VIEWID_SCENE); - if (view == NULL) { - DebugPrintf("The scene view isn't currently active\n"); - } else if (argc != 2 && argc != 3) { - DebugPrintf("Usage: %s <anim resource (*.aa)> <fullscreen>\n", argv[0]); - DebugPrintf("If fullscreen is 1, the screen palette is replaced with the palette of the animation\n"); - } else { - char resourceName[20]; - strncpy(resourceName, argv[1], 15); - resourceName[15] = '\0'; - if (!strchr(resourceName, '.')) - strcat(resourceName, ".AA"); - - _vm->_viewManager->moveToFront(view); - if (argc == 3 && atoi(argv[2]) == 1) - _madsVm->_palette->deleteAllRanges(); - - _madsVm->scene()->_sceneAnimation->load(resourceName, 0); - - view->restore(0, 0, view->width(), view->height()); - return false; - } - - return true; -} - /*--------------------------------------------------------------------------*/ M4Console::M4Console(M4Engine *vm): Console(vm) { diff --git a/engines/m4/console.h b/engines/m4/console.h index 53a47dada9..b592f041cf 100644 --- a/engines/m4/console.h +++ b/engines/m4/console.h @@ -50,6 +50,7 @@ private: bool cmdStartConversation(int argc, const char **argv); bool cmdShowTextview(int argc, const char **argv); bool cmdShowAnimview(int argc, const char **argv); + bool cmdPlayAnimation(int argc, const char **argv); public: Console(MadsM4Engine *vm); @@ -63,8 +64,6 @@ private: bool cmdObject(int argc, const char **argv); bool cmdMessage(int argc, const char **argv); bool cmdSceneInfo(int argc, const char **argv); - bool cmdPlayAnimation(int argc, const char **argv); - public: MadsConsole(MadsEngine *vm); virtual ~MadsConsole() {} diff --git a/engines/m4/converse.cpp b/engines/m4/converse.cpp index af26a86313..aab2fc95ce 100644 --- a/engines/m4/converse.cpp +++ b/engines/m4/converse.cpp @@ -96,7 +96,7 @@ void ConversationView::setNode(int32 nodeIndex) { _vm->_font->setFont(FONT_CONVERSATION); // TODO: Conversation styles and colors - _vm->_font->current()->setColours(2, 1, 3); + _vm->_font->setColors(2, 1, 3); _currentNodeIndex = nodeIndex; @@ -124,7 +124,7 @@ void ConversationView::setNode(int32 nodeIndex) { } // Figure out the longest string to determine where option highlighting ends - int tempX = _vm->_font->current()->getWidth(node->entries[i]->text, 0) + + int tempX = _vm->_font->getWidth(node->entries[i]->text, 0) + CONV_ENTRIES_X_OFFSET + 10; _xEnd = MAX(_xEnd, tempX); } @@ -163,10 +163,10 @@ void ConversationView::onRefresh(RectList *rects, M4Surface *destSurface) { if (i > CONV_MAX_SHOWN_ENTRIES - 1) break; - _vm->_font->current()->setColour((_highlightedIndex == i) ? CONVERSATION_ENTRY_HIGHLIGHTED : + _vm->_font->setColor((_highlightedIndex == i) ? CONVERSATION_ENTRY_HIGHLIGHTED : CONVERSATION_ENTRY_NORMAL); - _vm->_font->current()->writeString(this, _activeItems[i]->text, CONV_ENTRIES_X_OFFSET, + _vm->_font->writeString(this, _activeItems[i]->text, CONV_ENTRIES_X_OFFSET, CONV_ENTRIES_Y_OFFSET + CONV_ENTRIES_HEIGHT * i, 0, 0); } } diff --git a/engines/m4/dialogs.cpp b/engines/m4/dialogs.cpp index a7104537f5..3af94af262 100644 --- a/engines/m4/dialogs.cpp +++ b/engines/m4/dialogs.cpp @@ -127,7 +127,7 @@ void Dialog::writeChars(const char *srcLine) { strcat(line, wordStr); lineLen = strlen(line); - lineWidth = _vm->_font->current()->getWidth(line, DIALOG_SPACING); + lineWidth = _vm->_font->getWidth(line, DIALOG_SPACING); if (((_lineX + lineLen) > _widthChars) || ((_widthX + lineWidth) > _dialogWidth)) { incLine(); @@ -146,7 +146,7 @@ void Dialog::writeChars(const char *srcLine) { */ void Dialog::appendText(const char *line) { _lineX += strlen(line); - _widthX += _vm->_font->current()->getWidth(line, DIALOG_SPACING); + _widthX += _vm->_font->getWidth(line, DIALOG_SPACING); strcat(_lines[_lines.size() - 1].data, line); } @@ -158,7 +158,7 @@ void Dialog::addLine(const char *line, bool underlineP) { if ((_widthX > 0) || (_lineX > 0)) incLine(); - int lineWidth = _vm->_font->current()->getWidth(line, DIALOG_SPACING); + int lineWidth = _vm->_font->getWidth(line, DIALOG_SPACING); int lineLen = strlen(line); if ((lineWidth > _dialogWidth) || (lineLen >= _widthChars)) @@ -383,7 +383,7 @@ Dialog::Dialog(MadsM4Engine *vm, const char *msgData, const char *title): View(v if (id > 0) { // Suffix provided - specifies the dialog width in number of chars _widthChars = id * 2; - _dialogWidth = id * (_vm->_font->current()->getMaxWidth() + DIALOG_SPACING) + 10; + _dialogWidth = id * (_vm->_font->getMaxWidth() + DIALOG_SPACING) + 10; } } else if (matchCommand(cmdText, "UNDER")) { @@ -416,7 +416,7 @@ Dialog::Dialog(MadsM4Engine *vm, const char *msgData, const char *title): View(v Dialog::Dialog(MadsM4Engine *vm, int widthChars): View(vm, Common::Rect(0, 0, 0, 0)) { _vm->_font->setFont(FONT_INTERFACE_MADS); _widthChars = widthChars * 2; - _dialogWidth = widthChars * (_vm->_font->current()->getMaxWidth() + DIALOG_SPACING) + 10; + _dialogWidth = widthChars * (_vm->_font->getMaxWidth() + DIALOG_SPACING) + 10; _screenType = LAYER_DIALOG; _lineX = 0; _widthX = 0; @@ -439,7 +439,7 @@ void Dialog::draw() { // Calculate bounds int dlgWidth = _dialogWidth; - int dlgHeight = _lines.size() * (_vm->_font->current()->getHeight() + 1) + 10; + int dlgHeight = _lines.size() * (_vm->_font->getHeight() + 1) + 10; int dialogX = (_vm->_screen->width() - dlgWidth) / 2; int dialogY = (_vm->_screen->height() - dlgHeight) / 2; @@ -480,26 +480,26 @@ void Dialog::draw() { } // Handle drawing the text contents - _vm->_font->current()->setColours(7, 7, 7); + _vm->_font->setColours(7, 7, 7); setColour(7); - for (uint lineCtr = 0, yp = 5; lineCtr < _lines.size(); ++lineCtr, yp += _vm->_font->current()->getHeight() + 1) { + for (uint lineCtr = 0, yp = 5; lineCtr < _lines.size(); ++lineCtr, yp += _vm->_font->getHeight() + 1) { if (_lines[lineCtr].barLine) { // Bar separation line - hLine(5, width() - 6, ((_vm->_font->current()->getHeight() + 1) >> 1) + yp); + hLine(5, width() - 6, ((_vm->_font->getHeight() + 1) >> 1) + yp); } else { // Standard line Common::Point pt(_lines[lineCtr].xp + 5, yp); if (_lines[lineCtr].xp & 0x40) ++pt.y; - _vm->_font->current()->writeString(this, _lines[lineCtr].data, pt.x, pt.y, 0, DIALOG_SPACING); + _vm->_font->writeString(this, _lines[lineCtr].data, pt.x, pt.y, 0, DIALOG_SPACING); if (_lines[lineCtr].underline) // Underline needed - hLine(pt.x, pt.x + _vm->_font->current()->getWidth(_lines[lineCtr].data, DIALOG_SPACING), - pt.y + _vm->_font->current()->getHeight()); + hLine(pt.x, pt.x + _vm->_font->getWidth(_lines[lineCtr].data, DIALOG_SPACING), + pt.y + _vm->_font->getHeight()); } } @@ -528,7 +528,7 @@ void Dialog::display(MadsM4Engine *vm, int widthChars, const char **descEntries) dlg->incLine(); dlg->writeChars(*descEntries); - int lineWidth = vm->_font->current()->getWidth(*descEntries, DIALOG_SPACING); + int lineWidth = vm->_font->getWidth(*descEntries, DIALOG_SPACING); dlg->_lines[dlg->_lines.size() - 1].xp = (dlg->_dialogWidth - 10 - lineWidth) / 2; ++descEntries; } diff --git a/engines/m4/globals.h b/engines/m4/globals.h index 3fc31b4ec2..2bfedf1449 100644 --- a/engines/m4/globals.h +++ b/engines/m4/globals.h @@ -276,7 +276,6 @@ public: // DEPRECATED: ScummVM re-implementation keeps all the quotes loaded, so the methods below are stubs void clearQuotes() {} void loadQuoteRange(int startNum, int endNum) {} - void loadQuoteSet(...) {} void loadQuote(int quoteNum) {} void loadMadsMessagesInfo(); diff --git a/engines/m4/gui.cpp b/engines/m4/gui.cpp index 8665b4e767..8f949de9c5 100644 --- a/engines/m4/gui.cpp +++ b/engines/m4/gui.cpp @@ -290,26 +290,26 @@ void MenuButton::onRefresh() { case OBJTYPE_SL_TEXT: switch (_objectState) { case OS_MOUSEOVER: - _vm->_font->current()->setColours(TEXT_COLOR_MOUSEOVER_SHADOW, TEXT_COLOR_MOUSEOVER_FOREGROUND, + _vm->_font->setColors(TEXT_COLOR_MOUSEOVER_SHADOW, TEXT_COLOR_MOUSEOVER_FOREGROUND, TEXT_COLOR_MOUSEOVER_HILIGHT); sprite = sprites[SL_LINE_MOUSEOVER]; break; case OS_PRESSED: - _vm->_font->current()->setColours(TEXT_COLOR_PRESSED_SHADOW, TEXT_COLOR_PRESSED_FOREGROUND, + _vm->_font->setColors(TEXT_COLOR_PRESSED_SHADOW, TEXT_COLOR_PRESSED_FOREGROUND, TEXT_COLOR_PRESSED_HILIGHT); sprite = sprites[SL_LINE_PRESSED]; break; case OS_GREYED: - _vm->_font->current()->setColours(TEXT_COLOR_GREYED_SHADOW, TEXT_COLOR_GREYED_FOREGROUND, + _vm->_font->setColors(TEXT_COLOR_GREYED_SHADOW, TEXT_COLOR_GREYED_FOREGROUND, TEXT_COLOR_GREYED_HILIGHT); sprite = sprites[SL_LINE_NORMAL]; break; default: case OS_NORMAL: - _vm->_font->current()->setColours(TEXT_COLOR_NORMAL_SHADOW, TEXT_COLOR_NORMAL_FOREGROUND, + _vm->_font->setColors(TEXT_COLOR_NORMAL_SHADOW, TEXT_COLOR_NORMAL_FOREGROUND, TEXT_COLOR_NORMAL_HILIGHT); sprite = sprites[SL_LINE_NORMAL]; break; @@ -849,11 +849,11 @@ void MenuSaveLoadText::onRefresh() { if (_displayValue != 0) { char tempBuffer[5]; sprintf(tempBuffer, "%02d", _displayValue); - _vm->_font->current()->writeString(_parent, tempBuffer, xp, _bounds.top + 1, 0, -1); + _vm->_font->writeString(_parent, tempBuffer, xp, _bounds.top + 1, 0, -1); xp = _bounds.left + 26; } - _vm->_font->current()->writeString(_parent, _displayText, xp, _bounds.top + 1, 0, -1); + _vm->_font->writeString(_parent, _displayText, xp, _bounds.top + 1, 0, -1); } } @@ -955,18 +955,18 @@ void MenuTextField::onRefresh() { // Draw the text _vm->_font->setFont(FONT_MENU); - _vm->_font->current()->setColours(TEXT_COLOR_NORMAL_SHADOW, TEXT_COLOR_NORMAL_FOREGROUND, + _vm->_font->setColors(TEXT_COLOR_NORMAL_SHADOW, TEXT_COLOR_NORMAL_FOREGROUND, TEXT_COLOR_NORMAL_HILIGHT); int xp = _bounds.left + 4; if (_displayValue != 0) { char tempBuffer[5]; sprintf(tempBuffer, "%02d", _displayValue); - _vm->_font->current()->writeString(_parent, tempBuffer, xp, _bounds.top + 1, 0, -1); + _vm->_font->writeString(_parent, tempBuffer, xp, _bounds.top + 1, 0, -1); xp = _bounds.left + 26; } - _vm->_font->current()->writeString(_parent, _displayText, xp, _bounds.top + 1, 0, -1); + _vm->_font->writeString(_parent, _displayText, xp, _bounds.top + 1, 0, -1); if (focused) { // Draw in the cursor @@ -975,7 +975,7 @@ void MenuTextField::onRefresh() { // Get the width of the string up to the cursor position char tempCh = *_cursor; *_cursor = '\0'; - int stringWidth = _vm->_font->current()->getWidth(_displayText); + int stringWidth = _vm->_font->getWidth(_displayText); *_cursor = tempCh; parent()->setColor(TEXT_COLOR_MOUSEOVER_FOREGROUND); @@ -1015,10 +1015,10 @@ bool MenuTextField::onEvent(M4EventType event, int32 param, int x, int y, MenuOb tempP = &tempStr[tempLen]; _vm->_font->setFont(FONT_MENU); - tempLen = _vm->_font->current()->getWidth(tempStr); + tempLen = _vm->_font->getWidth(tempStr); while ((tempP != &tempStr[0]) && (tempLen > x - _bounds.left - 26)) { *--tempP = '\0'; - tempLen = _vm->_font->current()->getWidth(tempStr); + tempLen = _vm->_font->getWidth(tempStr); } _cursor = &_displayText[tempP - &tempStr[0]]; @@ -1098,7 +1098,7 @@ bool MenuTextField::onEvent(M4EventType event, int32 param, int x, int y, MenuOb parent()->_deleteSaveDesc = false; _vm->_font->setFont(FONT_MENU); - tempLen = _vm->_font->current()->getWidth(_displayText); + tempLen = _vm->_font->getWidth(_displayText); if ((strlen(_displayText) < MAX_SAVEGAME_NAME - 1) && (tempLen < _pixelWidth - 12) && (param >= 32) && (param <= 127)) { @@ -1140,9 +1140,9 @@ GUITextField::GUITextField(View *owner, const Common::Rect &bounds): GUIRect(own void GUITextField::onRefresh() { _parent->fillRect(_bounds, _vm->_palette->BLACK); - _vm->_font->current()->setColours(3, 3, 3); + _vm->_font->setColors(3, 3, 3); _vm->_font->setFont(FONT_INTERFACE); - _vm->_font->current()->writeString(_parent, _text.c_str(), _bounds.left, _bounds.top, 0, 1); + _vm->_font->writeString(_parent, _text.c_str(), _bounds.left, _bounds.top, 0, 1); } //-------------------------------------------------------------------------- diff --git a/engines/m4/m4.cpp b/engines/m4/m4.cpp index a999a6bd5a..824896ad33 100644 --- a/engines/m4/m4.cpp +++ b/engines/m4/m4.cpp @@ -145,6 +145,7 @@ MadsM4Engine::~MadsM4Engine() { delete _script; delete _ws; delete _random; + delete _animation; delete _palette; delete _globals; delete _sound; @@ -173,7 +174,7 @@ Common::Error MadsM4Engine::run() { _events = new Events(this); _kernel = new Kernel(this); _player = new Player(this); - _font = new FontManager(this); + _font = new Font(this); if (getGameType() == GType_Burger) { _actor = new Actor(this); _conversationView = new ConversationView(this); @@ -187,6 +188,7 @@ Common::Error MadsM4Engine::run() { _sound = new Sound(this, _mixer, 255); _script = new ScriptInterpreter(this); _ws = new WoodScript(this); + _animation = new Animation(this); //_callbacks = new Callbacks(this); _random = new Common::RandomSource(); g_eventRec.registerRandomSource(*_random, "m4"); @@ -555,9 +557,9 @@ Common::Error MadsEngine::run() { _scene->show(); _font->setFont(FONT_MAIN_MADS); - _font->current()->setColours(2, 1, 3); - _font->current()->writeString(_scene->getBackgroundSurface(), "Testing the M4/MADS ScummVM engine", 5, 160, 310, 2); - _font->current()->writeString(_scene->getBackgroundSurface(), "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 5, 180, 310, 2); + _font->setColors(2, 1, 3); + _font->writeString(_scene->getBackgroundSurface(), "Testing the M4/MADS ScummVM engine", 5, 160, 310, 2); + _font->writeString(_scene->getBackgroundSurface(), "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 5, 180, 310, 2); if (getGameType() == GType_DragonSphere) { //_scene->showMADSV2TextBox("Test", 10, 10, NULL); @@ -573,6 +575,8 @@ Common::Error MadsEngine::run() { while (!_events->quitFlag) { eventHandler(); + _animation->updateAnim(); + if (g_system->getMillis() >= nextFrame) { nextFrame = g_system->getMillis() + GAME_FRAME_DELAY; ++_currentTimer; diff --git a/engines/m4/m4.h b/engines/m4/m4.h index 3174c886d5..9e3035957a 100644 --- a/engines/m4/m4.h +++ b/engines/m4/m4.h @@ -192,7 +192,7 @@ public: Player *_player; Mouse *_mouse; Events *_events; - FontManager *_font; + Font *_font; Actor *_actor; Scene *_scene; Dialogs *_dialogs; @@ -203,6 +203,7 @@ public: Rails *_rails; ScriptInterpreter *_script; WoodScript *_ws; + Animation *_animation; Common::RandomSource *_random; Scene *scene() { return _scene; } diff --git a/engines/m4/m4_views.cpp b/engines/m4/m4_views.cpp index f4345787df..3d633cef0d 100644 --- a/engines/m4/m4_views.cpp +++ b/engines/m4/m4_views.cpp @@ -34,7 +34,7 @@ namespace M4 { GUIInventory::GUIInventory(View *owner, MadsM4Engine *vm, const Common::Rect &bounds, int horizCells, int vertCells, int cellWidth, int cellHeight, int tag): GUIRect(owner, bounds, tag) { - _vm = vm; + _vm = vm; _cellCount.x = horizCells; _cellCount.y = vertCells; _cellSize.x = cellWidth; diff --git a/engines/m4/mads_anim.cpp b/engines/m4/mads_anim.cpp index ca53bdca75..5e19e90533 100644 --- a/engines/m4/mads_anim.cpp +++ b/engines/m4/mads_anim.cpp @@ -37,7 +37,7 @@ namespace M4 { TextviewView::TextviewView(MadsM4Engine *vm): View(vm, Common::Rect(0, 0, vm->_screen->width(), vm->_screen->height())), _bgSurface(vm->_screen->width(), MADS_SURFACE_HEIGHT), - _textSurface(vm->_screen->width(), MADS_SURFACE_HEIGHT + vm->_font->current()->getHeight() + + _textSurface(vm->_screen->width(), MADS_SURFACE_HEIGHT + vm->_font->getHeight() + TEXTVIEW_LINE_SPACING) { _screenType = VIEWID_TEXTVIEW; @@ -60,7 +60,7 @@ TextviewView::TextviewView(MadsM4Engine *vm): _vm->_palette->setPalette(&palData[0], 4, 3); _vm->_palette->blockRange(4, 3); - _vm->_font->current()->setColours(5, 6, 4); + _vm->_font->setColors(5, 6, 4); clear(); _bgSurface.clear(); @@ -222,7 +222,7 @@ void TextviewView::updateState() { } } else { // Handling a text row - if (++_lineY == (_vm->_font->current()->getHeight() + TEXTVIEW_LINE_SPACING)) + if (++_lineY == (_vm->_font->getHeight() + TEXTVIEW_LINE_SPACING)) processLines(); } @@ -404,7 +404,7 @@ void TextviewView::processText() { if (!strcmp(_currentLine, "***")) { // Special signifier for end of script - _scrollCount = _vm->_font->current()->getHeight() * 13; + _scrollCount = _vm->_font->getHeight() * 13; _lineY = -1; return; } @@ -416,7 +416,7 @@ void TextviewView::processText() { char *centerP = strchr(_currentLine, '@'); if (centerP) { *centerP = '\0'; - xStart = (width() / 2) - _vm->_font->current()->getWidth(_currentLine); + xStart = (width() / 2) - _vm->_font->getWidth(_currentLine); // Delete the @ character and shift back the remainder of the string char *p = centerP + 1; @@ -424,16 +424,16 @@ void TextviewView::processText() { strcpy(centerP, p); } else { - lineWidth = _vm->_font->current()->getWidth(_currentLine); + lineWidth = _vm->_font->getWidth(_currentLine); xStart = (width() - lineWidth) / 2; } // Copy the text line onto the bottom of the textSurface surface, which will allow it // to gradually scroll onto the screen - int yp = _textSurface.height() - _vm->_font->current()->getHeight() - TEXTVIEW_LINE_SPACING; + int yp = _textSurface.height() - _vm->_font->getHeight() - TEXTVIEW_LINE_SPACING; _textSurface.fillRect(Common::Rect(0, yp, _textSurface.width(), _textSurface.height()), _vm->_palette->BLACK); - _vm->_font->current()->writeString(&_textSurface, _currentLine, xStart, yp); + _vm->_font->writeString(&_textSurface, _currentLine, xStart, yp); } diff --git a/engines/m4/mads_menus.cpp b/engines/m4/mads_menus.cpp index 810acb04fb..1ee6b3d265 100644 --- a/engines/m4/mads_menus.cpp +++ b/engines/m4/mads_menus.cpp @@ -617,10 +617,10 @@ void RexDialogView::initialiseLines() { } _totalTextEntries = 0; - // Set up a default sprite slot entry for a full screen refresh + // Set up a default sprite slot entry _spriteSlots.startIndex = 1; - _spriteSlots[0].spriteType = FULL_SCREEN_REFRESH; - _spriteSlots[0].seqIndex = -1; + _spriteSlots[0].spriteId = -2; + _spriteSlots[0].timerIndex = -1; } void RexDialogView::initialiseGraphics() { @@ -796,8 +796,8 @@ bool RexDialogView::onEvent(M4EventType eventType, int32 param1, int x, int y, b void RexDialogView::setFrame(int frameNumber, int depth) { int slotIndex = _spriteSlots.getIndex(); - _spriteSlots[slotIndex].spriteType = FOREGROUND_SPRITE; - _spriteSlots[slotIndex].seqIndex = 1; + _spriteSlots[slotIndex].spriteId = 1; + _spriteSlots[slotIndex].timerIndex = 1; _spriteSlots[slotIndex].spriteListIndex = 0; //_menuSpritesIndex; _spriteSlots[slotIndex].frameNumber = frameNumber; @@ -986,15 +986,15 @@ RexGameMenuDialog::RexGameMenuDialog(): RexDialogView() { void RexGameMenuDialog::addLines() { // Add the title - int top = MADS_Y_OFFSET - 2 - ((((_vm->_font->current()->getHeight() + 2) * 6) >> 1) - 78); + int top = MADS_Y_OFFSET - 2 - ((((_vm->_font->getHeight() + 2) * 6) >> 1) - 78); - addQuote(_vm->_font->current(), ALIGN_CENTER, 0, top, 10); + addQuote(_vm->_font, ALIGN_CENTER, 0, top, 10); // Loop for adding the option lines of the dialog top += 6; for (int idx = 0; idx < 5; ++idx) { - top += _vm->_font->current()->getHeight() + 1; - addQuote(_vm->_font->current(), ALIGN_CENTER, 0, top, 11 + idx); + top += _vm->_font->getHeight() + 1; + addQuote(_vm->_font, ALIGN_CENTER, 0, top, 11 + idx); } } @@ -1070,42 +1070,42 @@ void RexOptionsDialog::reload() { void RexOptionsDialog::addLines() { // Add the title - int top = MADS_Y_OFFSET - 2 - ((((_vm->_font->current()->getHeight() + 1) * 9 + 12) >> 1) - 78); + int top = MADS_Y_OFFSET - 2 - ((((_vm->_font->getHeight() + 1) * 9 + 12) >> 1) - 78); - addQuote(_vm->_font->current(), ALIGN_CENTER, 0, top, 16); + addQuote(_vm->_font, ALIGN_CENTER, 0, top, 16); // Music state line - top += _vm->_font->current()->getHeight() + 1 + 6; - addQuote(_vm->_font->current(), ALIGN_CHAR_CENTER, 0, top, 17, _tempConfig.musicFlag ? 24 : 25); + top += _vm->_font->getHeight() + 1 + 6; + addQuote(_vm->_font, ALIGN_CHAR_CENTER, 0, top, 17, _tempConfig.musicFlag ? 24 : 25); // Sound state line - top += _vm->_font->current()->getHeight() + 1; - addQuote(_vm->_font->current(), ALIGN_CHAR_CENTER, 0, top, 18, _tempConfig.soundFlag ? 26 : 27); + top += _vm->_font->getHeight() + 1; + addQuote(_vm->_font, ALIGN_CHAR_CENTER, 0, top, 18, _tempConfig.soundFlag ? 26 : 27); // Interface easy state line - top += _vm->_font->current()->getHeight() + 1; - addQuote(_vm->_font->current(), ALIGN_CHAR_CENTER, 0, top, 19, _tempConfig.easyMouse ? 29 : 28); + top += _vm->_font->getHeight() + 1; + addQuote(_vm->_font, ALIGN_CHAR_CENTER, 0, top, 19, _tempConfig.easyMouse ? 29 : 28); // Inventory sppinng state line - top += _vm->_font->current()->getHeight() + 1; - addQuote(_vm->_font->current(), ALIGN_CHAR_CENTER, 0, top, 20, _tempConfig.invObjectsStill ? 31 : 30); + top += _vm->_font->getHeight() + 1; + addQuote(_vm->_font, ALIGN_CHAR_CENTER, 0, top, 20, _tempConfig.invObjectsStill ? 31 : 30); // Text window state line - top += _vm->_font->current()->getHeight() + 1; - addQuote(_vm->_font->current(), ALIGN_CHAR_CENTER, 0, top, 21, _tempConfig.textWindowStill ? 33 : 32); + top += _vm->_font->getHeight() + 1; + addQuote(_vm->_font, ALIGN_CHAR_CENTER, 0, top, 21, _tempConfig.textWindowStill ? 33 : 32); // Screen fade state line - top += _vm->_font->current()->getHeight() + 1; - addQuote(_vm->_font->current(), ALIGN_CHAR_CENTER, 0, top, 22, _tempConfig.screenFades + 34); + top += _vm->_font->getHeight() + 1; + addQuote(_vm->_font, ALIGN_CHAR_CENTER, 0, top, 22, _tempConfig.screenFades + 34); // Storyline mode line - top += _vm->_font->current()->getHeight() + 1; - addQuote(_vm->_font->current(), ALIGN_CHAR_CENTER, 0, top, 23, (_tempConfig.storyMode == 1) ? 37 : 38); + top += _vm->_font->getHeight() + 1; + addQuote(_vm->_font, ALIGN_CHAR_CENTER, 0, top, 23, (_tempConfig.storyMode == 1) ? 37 : 38); // Add Done and Cancel button texts - top += _vm->_font->current()->getHeight() + 1 + 6; - addQuote(_vm->_font->current(), ALIGN_CENTER, -54, top, 1, 0); - addQuote(_vm->_font->current(), ALIGN_CENTER, 54, top, 2, 0); + top += _vm->_font->getHeight() + 1 + 6; + addQuote(_vm->_font, ALIGN_CENTER, -54, top, 1, 0); + addQuote(_vm->_font, ALIGN_CENTER, 54, top, 2, 0); } bool RexOptionsDialog::onEvent(M4EventType eventType, int32 param1, int x, int y, bool &captureEvents) { diff --git a/engines/metaengine.h b/engines/metaengine.h index 7519feaaa4..964eee071c 100644 --- a/engines/metaengine.h +++ b/engines/metaengine.h @@ -231,6 +231,7 @@ private: friend class Common::Singleton<SingletonBaseType>; public: + GameDescriptor findGameOnePlugAtATime(const Common::String &gameName, const EnginePlugin **plugin = NULL) const; GameDescriptor findGame(const Common::String &gameName, const EnginePlugin **plugin = NULL) const; GameList detectGames(const Common::FSList &fslist) const; const EnginePlugin::List &getPlugins() const; diff --git a/engines/mohawk/video/cinepak.cpp b/engines/mohawk/video/cinepak.cpp new file mode 100644 index 0000000000..2ffe6869ae --- /dev/null +++ b/engines/mohawk/video/cinepak.cpp @@ -0,0 +1,286 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "mohawk/video/cinepak.h" + +#include "common/system.h" +#include "graphics/conversion.h" // For YUV2RGB + +// Code here partially based off of ffmpeg ;) + +namespace Mohawk { + +#define PUT_PIXEL(offset, lum, u, v) \ + Graphics::CPYUV2RGB(lum, u, v, r, g, b); \ + if (_pixelFormat.bytesPerPixel == 2) \ + *((uint16 *)_curFrame.surface->pixels + offset) = _pixelFormat.RGBToColor(r, g, b); \ + else \ + *((uint32 *)_curFrame.surface->pixels + offset) = _pixelFormat.RGBToColor(r, g, b) + +CinepakDecoder::CinepakDecoder() : Graphics::Codec() { + _curFrame.surface = NULL; + _curFrame.strips = NULL; + _y = 0; + _pixelFormat = g_system->getScreenFormat(); + + // We're going to have to dither if we're running in 8bpp. + // We'll take RGBA8888 for best color performance in this case. + if (_pixelFormat.bytesPerPixel == 1) + _pixelFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); +} + +CinepakDecoder::~CinepakDecoder() { + if (_curFrame.surface) + _curFrame.surface->free(); + delete[] _curFrame.strips; +} + +Graphics::Surface *CinepakDecoder::decodeImage(Common::SeekableReadStream *stream) { + _curFrame.flags = stream->readByte(); + _curFrame.length = (stream->readByte() << 16) + stream->readUint16BE(); + _curFrame.width = stream->readUint16BE(); + _curFrame.height = stream->readUint16BE(); + _curFrame.stripCount = stream->readUint16BE(); + + if (_curFrame.strips == NULL) + _curFrame.strips = new CinepakStrip[_curFrame.stripCount]; + + debug (4, "Cinepak Frame: Width = %d, Height = %d, Strip Count = %d", _curFrame.width, _curFrame.height, _curFrame.stripCount); + +#if 0 + // Borrowed from FFMPEG. This should cut out the extra data Cinepak for Sega has (which is useless). + // The theory behind this is that this is here to confuse standard Cinepak decoders. But, we won't let that happen! ;) + if (_curFrame.length != (uint32)stream->size()) { + if (stream->readUint16BE() == 0xFE00) + stream->readUint32BE(); + } +#endif + + if (!_curFrame.surface) { + _curFrame.surface = new Graphics::Surface(); + _curFrame.surface->create(_curFrame.width, _curFrame.height, _pixelFormat.bytesPerPixel); + } + + // Reset the y variable. + _y = 0; + + for (uint16 i = 0; i < _curFrame.stripCount; i++) { + if (i > 0 && !(_curFrame.flags & 1)) { // Use codebooks from last strip + for (uint16 j = 0; j < 256; j++) { + _curFrame.strips[i].v1_codebook[j] = _curFrame.strips[i - 1].v1_codebook[j]; + _curFrame.strips[i].v4_codebook[j] = _curFrame.strips[i - 1].v4_codebook[j]; + } + } + + _curFrame.strips[i].id = stream->readUint16BE(); + _curFrame.strips[i].length = stream->readUint16BE() - 12; // Subtract the 12 byte header + _curFrame.strips[i].rect.top = _y; stream->readUint16BE(); // Ignore, substitute with our own. + _curFrame.strips[i].rect.left = 0; stream->readUint16BE(); // Ignore, substitute with our own + _curFrame.strips[i].rect.bottom = _y + stream->readUint16BE(); + _curFrame.strips[i].rect.right = _curFrame.width; stream->readUint16BE(); // Ignore, substitute with our own + + //printf ("Left = %d, Top = %d, Right = %d, Bottom = %d\n", _curFrame.strips[i].rect.left, _curFrame.strips[i].rect.top, _curFrame.strips[i].rect.right, _curFrame.strips[i].rect.bottom); + + // Sanity check. Because Cinepak is based on 4x4 blocks, the width and height of each strip needs to be divisible by 4. + assert(!(_curFrame.strips[i].rect.width() % 4) && !(_curFrame.strips[i].rect.height() % 4)); + + uint32 pos = stream->pos(); + + while ((uint32)stream->pos() < (pos + _curFrame.strips[i].length) && !stream->eos()) { + byte chunkID = stream->readByte(); + + if (stream->eos()) + break; + + // Chunk Size is 24-bit, ignore the first 4 bytes + uint32 chunkSize = stream->readByte() << 16; + chunkSize += stream->readUint16BE() - 4; + + int32 startPos = stream->pos(); + + switch (chunkID) { + case 0x20: + case 0x21: + case 0x24: + case 0x25: + loadCodebook(stream, i, 4, chunkID, chunkSize); + break; + case 0x22: + case 0x23: + case 0x26: + case 0x27: + loadCodebook(stream, i, 1, chunkID, chunkSize); + break; + case 0x30: + case 0x31: + case 0x32: + decodeVectors(stream, i, chunkID, chunkSize); + break; + default: + warning("Unknown Cinepak chunk ID %02x", chunkID); + return _curFrame.surface; + } + + if (stream->pos() != startPos + (int32)chunkSize) + stream->seek(startPos + chunkSize); + } + + _y = _curFrame.strips[i].rect.bottom; + } + + return _curFrame.surface; +} + +void CinepakDecoder::loadCodebook(Common::SeekableReadStream *stream, uint16 strip, byte codebookType, byte chunkID, uint32 chunkSize) { + CinepakCodebook *codebook = (codebookType == 1) ? _curFrame.strips[strip].v1_codebook : _curFrame.strips[strip].v4_codebook; + + int32 startPos = stream->pos(); + uint32 flag = 0, mask = 0; + + for (uint16 i = 0; i < 256; i++) { + if ((chunkID & 0x01) && !(mask >>= 1)) { + if ((stream->pos() - startPos + 4) > (int32)chunkSize) + break; + + flag = stream->readUint32BE(); + mask = 0x80000000; + } + + if (!(chunkID & 0x01) || (flag & mask)) { + byte n = (chunkID & 0x04) ? 4 : 6; + if ((stream->pos() - startPos + n) > (int32)chunkSize) + break; + + for (byte j = 0; j < 4; j++) + codebook[i].y[j] = stream->readByte(); + + if (n == 6) { + codebook[i].u = stream->readByte() + 128; + codebook[i].v = stream->readByte() + 128; + } else { + /* this codebook type indicates either greyscale or + * palettized video; if palettized, U & V components will + * not be used so it is safe to set them to 128 for the + * benefit of greyscale rendering in YUV420P */ + codebook[i].u = 128; + codebook[i].v = 128; + } + } + } +} + +void CinepakDecoder::decodeVectors(Common::SeekableReadStream *stream, uint16 strip, byte chunkID, uint32 chunkSize) { + uint32 flag = 0, mask = 0; + uint32 iy[4]; + int32 startPos = stream->pos(); + byte r = 0, g = 0, b = 0; + + for (uint16 y = _curFrame.strips[strip].rect.top; y < _curFrame.strips[strip].rect.bottom; y += 4) { + iy[0] = _curFrame.strips[strip].rect.left + y * _curFrame.width; + iy[1] = iy[0] + _curFrame.width; + iy[2] = iy[1] + _curFrame.width; + iy[3] = iy[2] + _curFrame.width; + + for (uint16 x = _curFrame.strips[strip].rect.left; x < _curFrame.strips[strip].rect.right; x += 4) { + if ((chunkID & 0x01) && !(mask >>= 1)) { + if ((stream->pos() - startPos + 4) > (int32)chunkSize) + return; + + flag = stream->readUint32BE(); + mask = 0x80000000; + } + + if (!(chunkID & 0x01) || (flag & mask)) { + if (!(chunkID & 0x02) && !(mask >>= 1)) { + if ((stream->pos() - startPos + 4) > (int32)chunkSize) + return; + + flag = stream->readUint32BE(); + mask = 0x80000000; + } + + if ((chunkID & 0x02) || (~flag & mask)) { + if ((stream->pos() - startPos + 1) > (int32)chunkSize) + return; + + // Get the codebook + CinepakCodebook codebook = _curFrame.strips[strip].v1_codebook[stream->readByte()]; + + PUT_PIXEL(iy[0] + 0, codebook.y[0], codebook.u, codebook.v); + PUT_PIXEL(iy[0] + 1, codebook.y[0], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 0, codebook.y[0], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 1, codebook.y[0], codebook.u, codebook.v); + + PUT_PIXEL(iy[0] + 2, codebook.y[1], codebook.u, codebook.v); + PUT_PIXEL(iy[0] + 3, codebook.y[1], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 2, codebook.y[1], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 3, codebook.y[1], codebook.u, codebook.v); + + PUT_PIXEL(iy[2] + 0, codebook.y[2], codebook.u, codebook.v); + PUT_PIXEL(iy[2] + 1, codebook.y[2], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 0, codebook.y[2], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 1, codebook.y[2], codebook.u, codebook.v); + + PUT_PIXEL(iy[2] + 2, codebook.y[3], codebook.u, codebook.v); + PUT_PIXEL(iy[2] + 3, codebook.y[3], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 2, codebook.y[3], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 3, codebook.y[3], codebook.u, codebook.v); + } else if (flag & mask) { + if ((stream->pos() - startPos + 4) > (int32)chunkSize) + return; + + CinepakCodebook codebook = _curFrame.strips[strip].v4_codebook[stream->readByte()]; + PUT_PIXEL(iy[0] + 0, codebook.y[0], codebook.u, codebook.v); + PUT_PIXEL(iy[0] + 1, codebook.y[1], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 0, codebook.y[2], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 1, codebook.y[3], codebook.u, codebook.v); + + codebook = _curFrame.strips[strip].v4_codebook[stream->readByte()]; + PUT_PIXEL(iy[0] + 2, codebook.y[0], codebook.u, codebook.v); + PUT_PIXEL(iy[0] + 3, codebook.y[1], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 2, codebook.y[2], codebook.u, codebook.v); + PUT_PIXEL(iy[1] + 3, codebook.y[3], codebook.u, codebook.v); + + codebook = _curFrame.strips[strip].v4_codebook[stream->readByte()]; + PUT_PIXEL(iy[2] + 0, codebook.y[0], codebook.u, codebook.v); + PUT_PIXEL(iy[2] + 1, codebook.y[1], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 0, codebook.y[2], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 1, codebook.y[3], codebook.u, codebook.v); + + codebook = _curFrame.strips[strip].v4_codebook[stream->readByte()]; + PUT_PIXEL(iy[2] + 2, codebook.y[0], codebook.u, codebook.v); + PUT_PIXEL(iy[2] + 3, codebook.y[1], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 2, codebook.y[2], codebook.u, codebook.v); + PUT_PIXEL(iy[3] + 3, codebook.y[3], codebook.u, codebook.v); + } + } + + for (byte i = 0; i < 4; i++) + iy[i] += 4; + } + } +} + +} // End of namespace Mohawk diff --git a/engines/mohawk/video/cinepak.h b/engines/mohawk/video/cinepak.h new file mode 100644 index 0000000000..3f4cbba17c --- /dev/null +++ b/engines/mohawk/video/cinepak.h @@ -0,0 +1,81 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef CINEPAK_H +#define CINEPAK_H + +#include "common/scummsys.h" +#include "common/stream.h" +#include "common/rect.h" +#include "graphics/surface.h" +#include "graphics/pixelformat.h" + +#include "graphics/video/codecs/codec.h" + +namespace Mohawk { + +struct CinepakCodebook { + byte y[4]; + byte u, v; +}; + +struct CinepakStrip { + uint16 id; + uint16 length; + Common::Rect rect; + CinepakCodebook v1_codebook[256], v4_codebook[256]; +}; + +struct CinepakFrame { + byte flags; + uint32 length; + uint16 width; + uint16 height; + uint16 stripCount; + CinepakStrip *strips; + + Graphics::Surface *surface; +}; + +class CinepakDecoder : public Graphics::Codec { +public: + CinepakDecoder(); + ~CinepakDecoder(); + + Graphics::Surface *decodeImage(Common::SeekableReadStream *stream); + Graphics::PixelFormat getPixelFormat() const { return _pixelFormat; } + +private: + CinepakFrame _curFrame; + int32 _y; + Graphics::PixelFormat _pixelFormat; + + void loadCodebook(Common::SeekableReadStream *stream, uint16 strip, byte codebookType, byte chunkID, uint32 chunkSize); + void decodeVectors(Common::SeekableReadStream *stream, uint16 strip, byte chunkID, uint32 chunkSize); +}; + +} + +#endif diff --git a/engines/mohawk/video/qdm2.cpp b/engines/mohawk/video/qdm2.cpp new file mode 100644 index 0000000000..b91440f00d --- /dev/null +++ b/engines/mohawk/video/qdm2.cpp @@ -0,0 +1,3063 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// Based off ffmpeg's QDM2 decoder + +#include "mohawk/video/qdm2.h" +#include "mohawk/video/qdm2data.h" + +#include "common/system.h" + +namespace Mohawk { + +// Fix compilation for non C99-compliant compilers, like MSVC +#ifndef int64_t +typedef signed long long int int64_t; +#endif + +// Integer log2 function. This is much faster than invoking +// double precision C99 log2 math functions or equivalent, since +// this is only used to determine maximum number of bits needed +// i.e. only non-fractional part is needed. Also, the double +// version is incorrect for exact cases due to floating point +// rounding errors. +static inline int scummvm_log2(int n) { + int ret = -1; + while(n != 0) { + n /= 2; + ret++; + } + return ret; +} + +#define QDM2_LIST_ADD(list, size, packet) \ + do { \ + if (size > 0) \ + list[size - 1].next = &list[size]; \ + list[size].packet = packet; \ + list[size].next = NULL; \ + size++; \ + } while(0) + +// Result is 8, 16 or 30 +#define QDM2_SB_USED(subSampling) (((subSampling) >= 2) ? 30 : 8 << (subSampling)) + +#define FIX_NOISE_IDX(noiseIdx) \ + if ((noiseIdx) >= 3840) \ + (noiseIdx) -= 3840 \ + +#define SB_DITHERING_NOISE(sb, noiseIdx) (_noiseTable[(noiseIdx)++] * sb_noise_attenuation[(sb)]) + +static inline void initGetBits(GetBitContext *s, const uint8 *buffer, int bitSize) { + int bufferSize = (bitSize + 7) >> 3; + + debug(1, "void initGetBits(GetBitContext *s, const uint8 *buffer, int bitSize)"); + + if (bufferSize < 0 || bitSize < 0) { + bufferSize = bitSize = 0; + buffer = NULL; + } + + s->buffer = buffer; + s->sizeInBits = bitSize; + s->bufferEnd = buffer + bufferSize; + s->index = 0; +} + +static inline int getBitsCount(GetBitContext *s) { + debug(1, "int getBitsCount(GetBitContext *s)"); + return s->index; +} + +static inline unsigned int getBits1(GetBitContext *s) { + int index; + uint8 result; + + debug(1, "unsigned int getBits1(GetBitContext *s)"); + + index = s->index; + result = s->buffer[index >> 3]; + + debug(1, "index : %d", index); + + result >>= (index & 0x07); + result &= 1; + index++; + s->index = index; + + return result; +} + +static inline unsigned int getBits(GetBitContext *s, int n) { + int tmp, reCache, reIndex; + + debug(1, "unsigned int getBits(GetBitContext *s, int n)"); + + reIndex = s->index; + + debug(1, "reIndex : %d", reIndex); + + reCache = READ_LE_UINT32((const uint8 *)s->buffer + (reIndex >> 3)) >> (reIndex & 0x07); + + tmp = (reCache) & ((uint32)0xffffffff >> (32 - n)); + + s->index = reIndex + n; + + return tmp; +} + +static inline void skipBits(GetBitContext *s, int n) { + int reIndex, reCache; + + debug(1, "void skipBits(GetBitContext *s, int n)"); + + reIndex = s->index; + reCache = 0; + + debug(1, "reIndex : %d", reIndex); + + reCache = READ_LE_UINT32((const uint8 *)s->buffer + (reIndex >> 3)) >> (reIndex & 0x07); + s->index = reIndex + n; +} + +#define BITS_LEFT(length, gb) ((length) - getBitsCount((gb))) + +static int splitRadixPermutation(int i, int n, int inverse) { + if (n <= 2) + return i & 1; + + int m = n >> 1; + + if(!(i & m)) + return splitRadixPermutation(i, m, inverse) * 2; + + m >>= 1; + + if (inverse == !(i & m)) + return splitRadixPermutation(i, m, inverse) * 4 + 1; + + return splitRadixPermutation(i, m, inverse) * 4 - 1; +} + +// sin(2*pi*x/n) for 0<=x<n/4, followed by n/2<=x<3n/4 +float ff_sin_16[8]; +float ff_sin_32[16]; +float ff_sin_64[32]; +float ff_sin_128[64]; +float ff_sin_256[128]; +float ff_sin_512[256]; +float ff_sin_1024[512]; +float ff_sin_2048[1024]; +float ff_sin_4096[2048]; +float ff_sin_8192[4096]; +float ff_sin_16384[8192]; +float ff_sin_32768[16384]; +float ff_sin_65536[32768]; + +float *ff_sin_tabs[] = { + NULL, NULL, NULL, NULL, + ff_sin_16, ff_sin_32, ff_sin_64, ff_sin_128, ff_sin_256, ff_sin_512, ff_sin_1024, + ff_sin_2048, ff_sin_4096, ff_sin_8192, ff_sin_16384, ff_sin_32768, ff_sin_65536, +}; + +// cos(2*pi*x/n) for 0<=x<=n/4, followed by its reverse +float ff_cos_16[8]; +float ff_cos_32[16]; +float ff_cos_64[32]; +float ff_cos_128[64]; +float ff_cos_256[128]; +float ff_cos_512[256]; +float ff_cos_1024[512]; +float ff_cos_2048[1024]; +float ff_cos_4096[2048]; +float ff_cos_8192[4096]; +float ff_cos_16384[8192]; +float ff_cos_32768[16384]; +float ff_cos_65536[32768]; + +float *ff_cos_tabs[] = { + NULL, NULL, NULL, NULL, + ff_cos_16, ff_cos_32, ff_cos_64, ff_cos_128, ff_cos_256, ff_cos_512, ff_cos_1024, + ff_cos_2048, ff_cos_4096, ff_cos_8192, ff_cos_16384, ff_cos_32768, ff_cos_65536, +}; + +void initCosineTables(int index) { + int m = 1 << index; + double freq = 2 * PI / m; + float *tab = ff_cos_tabs[index]; + + for (int i = 0; i <= m / 4; i++) + tab[i] = cos(i * freq); + + for (int i = 1; i < m / 4; i++) + tab[m / 2 - i] = tab[i]; +} + +void fftPermute(FFTContext *s, FFTComplex *z) { + const uint16 *revtab = s->revtab; + int np = 1 << s->nbits; + + if (s->tmpBuf) { + // TODO: handle split-radix permute in a more optimal way, probably in-place + for (int j = 0; j < np; j++) + s->tmpBuf[revtab[j]] = z[j]; + memcpy(z, s->tmpBuf, np * sizeof(FFTComplex)); + return; + } + + // reverse + for (int j = 0; j < np; j++) { + int k = revtab[j]; + if (k < j) { + FFTComplex tmp = z[k]; + z[k] = z[j]; + z[j] = tmp; + } + } +} + +#define DECL_FFT(n,n2,n4) \ +static void fft##n(FFTComplex *z) { \ + fft##n2(z); \ + fft##n4(z + n4 * 2); \ + fft##n4(z + n4 * 3); \ + pass(z, ff_cos_##n, n4 / 2); \ +} + +#ifndef M_SQRT1_2 +#define M_SQRT1_2 7.0710678118654752440E-1 +#endif + +#define sqrthalf (float)M_SQRT1_2 + +#define BF(x,y,a,b) { \ + x = a - b; \ + y = a + b; \ +} + +#define BUTTERFLIES(a0, a1, a2, a3) { \ + BF(t3, t5, t5, t1); \ + BF(a2.re, a0.re, a0.re, t5); \ + BF(a3.im, a1.im, a1.im, t3); \ + BF(t4, t6, t2, t6); \ + BF(a3.re, a1.re, a1.re, t4); \ + BF(a2.im, a0.im, a0.im, t6); \ +} + +// force loading all the inputs before storing any. +// this is slightly slower for small data, but avoids store->load aliasing +// for addresses separated by large powers of 2. +#define BUTTERFLIES_BIG(a0, a1, a2, a3) { \ + float r0 = a0.re, i0 = a0.im, r1 = a1.re, i1 = a1.im; \ + BF(t3, t5, t5, t1); \ + BF(a2.re, a0.re, r0, t5); \ + BF(a3.im, a1.im, i1, t3); \ + BF(t4, t6, t2, t6); \ + BF(a3.re, a1.re, r1, t4); \ + BF(a2.im, a0.im, i0, t6); \ +} + +#define TRANSFORM(a0, a1, a2, a3, wre, wim) { \ + t1 = a2.re * wre + a2.im * wim; \ + t2 = a2.im * wre - a2.re * wim; \ + t5 = a3.re * wre - a3.im * wim; \ + t6 = a3.im * wre + a3.re * wim; \ + BUTTERFLIES(a0, a1, a2, a3) \ +} + +#define TRANSFORM_ZERO(a0, a1, a2, a3) { \ + t1 = a2.re; \ + t2 = a2.im; \ + t5 = a3.re; \ + t6 = a3.im; \ + BUTTERFLIES(a0, a1, a2, a3) \ +} + +// z[0...8n-1], w[1...2n-1] +#define PASS(name) \ +static void name(FFTComplex *z, const float *wre, unsigned int n) { \ + float t1, t2, t3, t4, t5, t6; \ + int o1 = 2 * n; \ + int o2 = 4 * n; \ + int o3 = 6 * n; \ + const float *wim = wre + o1; \ + n--; \ + \ + TRANSFORM_ZERO(z[0], z[o1], z[o2], z[o3]); \ + TRANSFORM(z[1], z[o1 + 1], z[o2 + 1], z[o3 + 1], wre[1], wim[-1]); \ + \ + do { \ + z += 2; \ + wre += 2; \ + wim -= 2; \ + TRANSFORM(z[0], z[o1], z[o2], z[o3], wre[0], wim[0]); \ + TRANSFORM(z[1], z[o1 + 1],z[o2 + 1], z[o3 + 1], wre[1], wim[-1]); \ + } while(--n); \ +} + +PASS(pass) +#undef BUTTERFLIES +#define BUTTERFLIES BUTTERFLIES_BIG +PASS(pass_big) + +static void fft4(FFTComplex *z) { + float t1, t2, t3, t4, t5, t6, t7, t8; + + BF(t3, t1, z[0].re, z[1].re); + BF(t8, t6, z[3].re, z[2].re); + BF(z[2].re, z[0].re, t1, t6); + BF(t4, t2, z[0].im, z[1].im); + BF(t7, t5, z[2].im, z[3].im); + BF(z[3].im, z[1].im, t4, t8); + BF(z[3].re, z[1].re, t3, t7); + BF(z[2].im, z[0].im, t2, t5); +} + +static void fft8(FFTComplex *z) { + float t1, t2, t3, t4, t5, t6, t7, t8; + + fft4(z); + + BF(t1, z[5].re, z[4].re, -z[5].re); + BF(t2, z[5].im, z[4].im, -z[5].im); + BF(t3, z[7].re, z[6].re, -z[7].re); + BF(t4, z[7].im, z[6].im, -z[7].im); + BF(t8, t1, t3, t1); + BF(t7, t2, t2, t4); + BF(z[4].re, z[0].re, z[0].re, t1); + BF(z[4].im, z[0].im, z[0].im, t2); + BF(z[6].re, z[2].re, z[2].re, t7); + BF(z[6].im, z[2].im, z[2].im, t8); + + TRANSFORM(z[1], z[3], z[5], z[7], sqrthalf, sqrthalf); +} + +#undef BF + +DECL_FFT(16,8,4) +DECL_FFT(32,16,8) +DECL_FFT(64,32,16) +DECL_FFT(128,64,32) +DECL_FFT(256,128,64) +DECL_FFT(512,256,128) +#define pass pass_big +DECL_FFT(1024,512,256) +DECL_FFT(2048,1024,512) +DECL_FFT(4096,2048,1024) +DECL_FFT(8192,4096,2048) +DECL_FFT(16384,8192,4096) +DECL_FFT(32768,16384,8192) +DECL_FFT(65536,32768,16384) + +void fftCalc(FFTContext *s, FFTComplex *z) { + static void (* const fftDispatch[])(FFTComplex*) = { + fft4, fft8, fft16, fft32, fft64, fft128, fft256, fft512, fft1024, + fft2048, fft4096, fft8192, fft16384, fft32768, fft65536, + }; + + fftDispatch[s->nbits - 2](z); +} + +// complex multiplication: p = a * b +#define CMUL(pre, pim, are, aim, bre, bim) \ +{\ + float _are = (are); \ + float _aim = (aim); \ + float _bre = (bre); \ + float _bim = (bim); \ + (pre) = _are * _bre - _aim * _bim; \ + (pim) = _are * _bim + _aim * _bre; \ +} + +/** + * Compute the middle half of the inverse MDCT of size N = 2^nbits, + * thus excluding the parts that can be derived by symmetry + * @param output N/2 samples + * @param input N/2 samples + */ +void imdctHalfC(FFTContext *s, float *output, const float *input) { + const uint16 *revtab = s->revtab; + const float *tcos = s->tcos; + const float *tsin = s->tsin; + FFTComplex *z = (FFTComplex *)output; + + int n = 1 << s->mdctBits; + int n2 = n >> 1; + int n4 = n >> 2; + int n8 = n >> 3; + + // pre rotation + const float *in1 = input; + const float *in2 = input + n2 - 1; + for (int k = 0; k < n4; k++) { + int j = revtab[k]; + CMUL(z[j].re, z[j].im, *in2, *in1, tcos[k], tsin[k]); + in1 += 2; + in2 -= 2; + } + + fftCalc(s, z); + + // post rotation + reordering + for (int k = 0; k < n8; k++) { + float r0, i0, r1, i1; + CMUL(r0, i1, z[n8 - k - 1].im, z[n8 - k - 1].re, tsin[n8 - k - 1], tcos[n8 - k - 1]); + CMUL(r1, i0, z[n8 + k].im, z[n8 + k].re, tsin[n8 + k], tcos[n8 + k]); + z[n8 - k - 1].re = r0; + z[n8 - k - 1].im = i0; + z[n8 + k].re = r1; + z[n8 + k].im = i1; + } +} + +/** + * Compute inverse MDCT of size N = 2^nbits + * @param output N samples + * @param input N/2 samples + */ +void imdctCalcC(FFTContext *s, float *output, const float *input) { + int n = 1 << s->mdctBits; + int n2 = n >> 1; + int n4 = n >> 2; + + imdctHalfC(s, output + n4, input); + + for (int k = 0; k < n4; k++) { + output[k] = -output[n2 - k - 1]; + output[n - k - 1] = output[n2 + k]; + } +} + +/** + * Compute MDCT of size N = 2^nbits + * @param input N samples + * @param out N/2 samples + */ +void mdctCalcC(FFTContext *s, float *out, const float *input) { + const uint16 *revtab = s->revtab; + const float *tcos = s->tcos; + const float *tsin = s->tsin; + FFTComplex *x = (FFTComplex *)out; + + int n = 1 << s->mdctBits; + int n2 = n >> 1; + int n4 = n >> 2; + int n8 = n >> 3; + int n3 = 3 * n4; + + // pre rotation + for (int i = 0; i < n8; i++) { + float re = -input[2 * i + 3 * n4] - input[n3 - 1 - 2 * i]; + float im = -input[n4 + 2 * i] + input[n4 - 1 - 2 * i]; + int j = revtab[i]; + CMUL(x[j].re, x[j].im, re, im, -tcos[i], tsin[i]); + + re = input[2 * i] - input[n2 - 1 - 2 * i]; + im = -(input[n2 + 2 * i] + input[n - 1 - 2 * i]); + j = revtab[n8 + i]; + CMUL(x[j].re, x[j].im, re, im, -tcos[n8 + i], tsin[n8 + i]); + } + + fftCalc(s, x); + + // post rotation + for (int i = 0; i < n8; i++) { + float r0, i0, r1, i1; + CMUL(i1, r0, x[n8 - i - 1].re, x[n8 - i - 1].im, -tsin[n8 - i - 1], -tcos[n8 - i - 1]); + CMUL(i0, r1, x[n8 + i].re, x[n8 + i].im, -tsin[n8 + i], -tcos[n8 + i]); + x[n8 - i - 1].re = r0; + x[n8 - i - 1].im = i0; + x[n8 + i].re = r1; + x[n8 + i].im = i1; + } +} + +int fftInit(FFTContext *s, int nbits, int inverse) { + int i, j, m, n; + float alpha, c1, s1, s2; + + if (nbits < 2 || nbits > 16) + goto fail; + + s->nbits = nbits; + n = 1 << nbits; + s->tmpBuf = NULL; + + s->exptab = (FFTComplex *)malloc((n / 2) * sizeof(FFTComplex)); + if (!s->exptab) + goto fail; + + s->revtab = (uint16 *)malloc(n * sizeof(uint16)); + if (!s->revtab) + goto fail; + s->inverse = inverse; + + s2 = inverse ? 1.0 : -1.0; + + s->fftPermute = fftPermute; + s->fftCalc = fftCalc; + s->imdctCalc = imdctCalcC; + s->imdctHalf = imdctHalfC; + s->mdctCalc = mdctCalcC; + s->splitRadix = 1; + + if (s->splitRadix) { + for (j = 4; j <= nbits; j++) + initCosineTables(j); + + for (i = 0; i < n; i++) + s->revtab[-splitRadixPermutation(i, n, s->inverse) & (n - 1)] = i; + + s->tmpBuf = (FFTComplex *)malloc(n * sizeof(FFTComplex)); + } else { + for (i = 0; i < n / 2; i++) { + alpha = 2 * PI * (float)i / (float)n; + c1 = cos(alpha); + s1 = sin(alpha) * s2; + s->exptab[i].re = c1; + s->exptab[i].im = s1; + } + + //int np = 1 << nbits; + //int nblocks = np >> 3; + //int np2 = np >> 1; + + // compute bit reverse table + for (i = 0; i < n; i++) { + m = 0; + + for (j = 0; j < nbits; j++) + m |= ((i >> j) & 1) << (nbits - j - 1); + + s->revtab[i] = m; + } + } + + return 0; + + fail: + free(&s->revtab); + free(&s->exptab); + free(&s->tmpBuf); + return -1; +} + +/** + * Sets up a real FFT. + * @param nbits log2 of the length of the input array + * @param trans the type of transform + */ +int rdftInit(RDFTContext *s, int nbits, RDFTransformType trans) { + int n = 1 << nbits; + const double theta = (trans == RDFT || trans == IRIDFT ? -1 : 1) * 2 * PI / n; + + s->nbits = nbits; + s->inverse = trans == IRDFT || trans == IRIDFT; + s->signConvention = trans == RIDFT || trans == IRIDFT ? 1 : -1; + + if (nbits < 4 || nbits > 16) + return -1; + + if (fftInit(&s->fft, nbits - 1, trans == IRDFT || trans == RIDFT) < 0) + return -1; + + initCosineTables(nbits); + s->tcos = ff_cos_tabs[nbits]; + s->tsin = ff_sin_tabs[nbits] + (trans == RDFT || trans == IRIDFT) * (n >> 2); + + for (int i = 0; i < n >> 2; i++) + s->tsin[i] = sin(i*theta); + + return 0; +} + +/** Map one real FFT into two parallel real even and odd FFTs. Then interleave + * the two real FFTs into one complex FFT. Unmangle the results. + * ref: http://www.engineeringproductivitytools.com/stuff/T0001/PT10.HTM + */ +void rdftCalc(RDFTContext *s, float *data) { + FFTComplex ev, od; + + const int n = 1 << s->nbits; + const float k1 = 0.5; + const float k2 = 0.5 - s->inverse; + const float *tcos = s->tcos; + const float *tsin = s->tsin; + + if (!s->inverse) { + fftPermute(&s->fft, (FFTComplex *)data); + fftCalc(&s->fft, (FFTComplex *)data); + } + + // i=0 is a special case because of packing, the DC term is real, so we + // are going to throw the N/2 term (also real) in with it. + ev.re = data[0]; + data[0] = ev.re + data[1]; + data[1] = ev.re - data[1]; + + int i; + + for (i = 1; i < n >> 2; i++) { + int i1 = i * 2; + int i2 = n - i1; + + // Separate even and odd FFTs + ev.re = k1 * (data[i1] + data[i2]); + od.im = -k2 * (data[i1] - data[i2]); + ev.im = k1 * (data[i1 + 1] - data[i2 + 1]); + od.re = k2 * (data[i1 + 1] + data[i2 + 1]); + + // Apply twiddle factors to the odd FFT and add to the even FFT + data[i1] = ev.re + od.re * tcos[i] - od.im * tsin[i]; + data[i1 + 1] = ev.im + od.im * tcos[i] + od.re * tsin[i]; + data[i2] = ev.re - od.re * tcos[i] + od.im * tsin[i]; + data[i2 + 1] = -ev.im + od.im * tcos[i] + od.re * tsin[i]; + } + + data[i * 2 + 1] = s->signConvention * data[i * 2 + 1]; + if (s->inverse) { + data[0] *= k1; + data[1] *= k1; + fftPermute(&s->fft, (FFTComplex*)data); + fftCalc(&s->fft, (FFTComplex*)data); + } +} + +// half mpeg encoding window (full precision) +const int32 ff_mpa_enwindow[257] = { + 0, -1, -1, -1, -1, -1, -1, -2, + -2, -2, -2, -3, -3, -4, -4, -5, + -5, -6, -7, -7, -8, -9, -10, -11, + -13, -14, -16, -17, -19, -21, -24, -26, + -29, -31, -35, -38, -41, -45, -49, -53, + -58, -63, -68, -73, -79, -85, -91, -97, + -104, -111, -117, -125, -132, -139, -147, -154, + -161, -169, -176, -183, -190, -196, -202, -208, + 213, 218, 222, 225, 227, 228, 228, 227, + 224, 221, 215, 208, 200, 189, 177, 163, + 146, 127, 106, 83, 57, 29, -2, -36, + -72, -111, -153, -197, -244, -294, -347, -401, + -459, -519, -581, -645, -711, -779, -848, -919, + -991, -1064, -1137, -1210, -1283, -1356, -1428, -1498, + -1567, -1634, -1698, -1759, -1817, -1870, -1919, -1962, + -2001, -2032, -2057, -2075, -2085, -2087, -2080, -2063, + 2037, 2000, 1952, 1893, 1822, 1739, 1644, 1535, + 1414, 1280, 1131, 970, 794, 605, 402, 185, + -45, -288, -545, -814, -1095, -1388, -1692, -2006, + -2330, -2663, -3004, -3351, -3705, -4063, -4425, -4788, + -5153, -5517, -5879, -6237, -6589, -6935, -7271, -7597, + -7910, -8209, -8491, -8755, -8998, -9219, -9416, -9585, + -9727, -9838, -9916, -9959, -9966, -9935, -9863, -9750, + -9592, -9389, -9139, -8840, -8492, -8092, -7640, -7134, + 6574, 5959, 5288, 4561, 3776, 2935, 2037, 1082, + 70, -998, -2122, -3300, -4533, -5818, -7154, -8540, + -9975,-11455,-12980,-14548,-16155,-17799,-19478,-21189, +-22929,-24694,-26482,-28289,-30112,-31947,-33791,-35640, +-37489,-39336,-41176,-43006,-44821,-46617,-48390,-50137, +-51853,-53534,-55178,-56778,-58333,-59838,-61289,-62684, +-64019,-65290,-66494,-67629,-68692,-69679,-70590,-71420, +-72169,-72835,-73415,-73908,-74313,-74630,-74856,-74992, + 75038 +}; + +void ff_mpa_synth_init(int16 *window) { + int i; + int32 v; + + // max = 18760, max sum over all 16 coefs : 44736 + for(i = 0; i < 257; i++) { + v = ff_mpa_enwindow[i]; + v = (v + 2) >> 2; + window[i] = v; + + if ((i & 63) != 0) + v = -v; + + if (i != 0) + window[512 - i] = v; + } +} + +static inline uint16 round_sample(int *sum) { + int sum1; + sum1 = (*sum) >> 14; + *sum &= (1 << 14)-1; + if (sum1 < (-0x7fff - 1)) + sum1 = (-0x7fff - 1); + if (sum1 > 0x7fff) + sum1 = 0x7fff; + return sum1; +} + +static inline int MULH(int a, int b) { + return ((int64_t)(a) * (int64_t)(b))>>32; +} + +// signed 16x16 -> 32 multiply add accumulate +#define MACS(rt, ra, rb) rt += (ra) * (rb) + +#define MLSS(rt, ra, rb) ((rt) -= (ra) * (rb)) + +#define SUM8(op, sum, w, p)\ +{\ + op(sum, (w)[0 * 64], (p)[0 * 64]);\ + op(sum, (w)[1 * 64], (p)[1 * 64]);\ + op(sum, (w)[2 * 64], (p)[2 * 64]);\ + op(sum, (w)[3 * 64], (p)[3 * 64]);\ + op(sum, (w)[4 * 64], (p)[4 * 64]);\ + op(sum, (w)[5 * 64], (p)[5 * 64]);\ + op(sum, (w)[6 * 64], (p)[6 * 64]);\ + op(sum, (w)[7 * 64], (p)[7 * 64]);\ +} + +#define SUM8P2(sum1, op1, sum2, op2, w1, w2, p) \ +{\ + tmp_s = p[0 * 64];\ + op1(sum1, (w1)[0 * 64], tmp_s);\ + op2(sum2, (w2)[0 * 64], tmp_s);\ + tmp_s = p[1 * 64];\ + op1(sum1, (w1)[1 * 64], tmp_s);\ + op2(sum2, (w2)[1 * 64], tmp_s);\ + tmp_s = p[2 * 64];\ + op1(sum1, (w1)[2 * 64], tmp_s);\ + op2(sum2, (w2)[2 * 64], tmp_s);\ + tmp_s = p[3 * 64];\ + op1(sum1, (w1)[3 * 64], tmp_s);\ + op2(sum2, (w2)[3 * 64], tmp_s);\ + tmp_s = p[4 * 64];\ + op1(sum1, (w1)[4 * 64], tmp_s);\ + op2(sum2, (w2)[4 * 64], tmp_s);\ + tmp_s = p[5 * 64];\ + op1(sum1, (w1)[5 * 64], tmp_s);\ + op2(sum2, (w2)[5 * 64], tmp_s);\ + tmp_s = p[6 * 64];\ + op1(sum1, (w1)[6 * 64], tmp_s);\ + op2(sum2, (w2)[6 * 64], tmp_s);\ + tmp_s = p[7 * 64];\ + op1(sum1, (w1)[7 * 64], tmp_s);\ + op2(sum2, (w2)[7 * 64], tmp_s);\ +} + +#define FIXHR(a) ((int)((a) * (1LL<<32) + 0.5)) + +// tab[i][j] = 1.0 / (2.0 * cos(pi*(2*k+1) / 2^(6 - j))) + +// cos(i*pi/64) + +#define COS0_0 FIXHR(0.50060299823519630134/2) +#define COS0_1 FIXHR(0.50547095989754365998/2) +#define COS0_2 FIXHR(0.51544730992262454697/2) +#define COS0_3 FIXHR(0.53104259108978417447/2) +#define COS0_4 FIXHR(0.55310389603444452782/2) +#define COS0_5 FIXHR(0.58293496820613387367/2) +#define COS0_6 FIXHR(0.62250412303566481615/2) +#define COS0_7 FIXHR(0.67480834145500574602/2) +#define COS0_8 FIXHR(0.74453627100229844977/2) +#define COS0_9 FIXHR(0.83934964541552703873/2) +#define COS0_10 FIXHR(0.97256823786196069369/2) +#define COS0_11 FIXHR(1.16943993343288495515/4) +#define COS0_12 FIXHR(1.48416461631416627724/4) +#define COS0_13 FIXHR(2.05778100995341155085/8) +#define COS0_14 FIXHR(3.40760841846871878570/8) +#define COS0_15 FIXHR(10.19000812354805681150/32) + +#define COS1_0 FIXHR(0.50241928618815570551/2) +#define COS1_1 FIXHR(0.52249861493968888062/2) +#define COS1_2 FIXHR(0.56694403481635770368/2) +#define COS1_3 FIXHR(0.64682178335999012954/2) +#define COS1_4 FIXHR(0.78815462345125022473/2) +#define COS1_5 FIXHR(1.06067768599034747134/4) +#define COS1_6 FIXHR(1.72244709823833392782/4) +#define COS1_7 FIXHR(5.10114861868916385802/16) + +#define COS2_0 FIXHR(0.50979557910415916894/2) +#define COS2_1 FIXHR(0.60134488693504528054/2) +#define COS2_2 FIXHR(0.89997622313641570463/2) +#define COS2_3 FIXHR(2.56291544774150617881/8) + +#define COS3_0 FIXHR(0.54119610014619698439/2) +#define COS3_1 FIXHR(1.30656296487637652785/4) + +#define COS4_0 FIXHR(0.70710678118654752439/2) + +/* butterfly operator */ +#define BF(a, b, c, s)\ +{\ + tmp0 = tab[a] + tab[b];\ + tmp1 = tab[a] - tab[b];\ + tab[a] = tmp0;\ + tab[b] = MULH(tmp1<<(s), c);\ +} + +#define BF1(a, b, c, d)\ +{\ + BF(a, b, COS4_0, 1);\ + BF(c, d,-COS4_0, 1);\ + tab[c] += tab[d];\ +} + +#define BF2(a, b, c, d)\ +{\ + BF(a, b, COS4_0, 1);\ + BF(c, d,-COS4_0, 1);\ + tab[c] += tab[d];\ + tab[a] += tab[c];\ + tab[c] += tab[b];\ + tab[b] += tab[d];\ +} + +#define ADD(a, b) tab[a] += tab[b] + +// DCT32 without 1/sqrt(2) coef zero scaling. +static void dct32(int32 *out, int32 *tab) { + int tmp0, tmp1; + + // pass 1 + BF( 0, 31, COS0_0 , 1); + BF(15, 16, COS0_15, 5); + // pass 2 + BF( 0, 15, COS1_0 , 1); + BF(16, 31,-COS1_0 , 1); + // pass 1 + BF( 7, 24, COS0_7 , 1); + BF( 8, 23, COS0_8 , 1); + // pass 2 + BF( 7, 8, COS1_7 , 4); + BF(23, 24,-COS1_7 , 4); + // pass 3 + BF( 0, 7, COS2_0 , 1); + BF( 8, 15,-COS2_0 , 1); + BF(16, 23, COS2_0 , 1); + BF(24, 31,-COS2_0 , 1); + // pass 1 + BF( 3, 28, COS0_3 , 1); + BF(12, 19, COS0_12, 2); + // pass 2 + BF( 3, 12, COS1_3 , 1); + BF(19, 28,-COS1_3 , 1); + // pass 1 + BF( 4, 27, COS0_4 , 1); + BF(11, 20, COS0_11, 2); + // pass 2 + BF( 4, 11, COS1_4 , 1); + BF(20, 27,-COS1_4 , 1); + // pass 3 + BF( 3, 4, COS2_3 , 3); + BF(11, 12,-COS2_3 , 3); + BF(19, 20, COS2_3 , 3); + BF(27, 28,-COS2_3 , 3); + // pass 4 + BF( 0, 3, COS3_0 , 1); + BF( 4, 7,-COS3_0 , 1); + BF( 8, 11, COS3_0 , 1); + BF(12, 15,-COS3_0 , 1); + BF(16, 19, COS3_0 , 1); + BF(20, 23,-COS3_0 , 1); + BF(24, 27, COS3_0 , 1); + BF(28, 31,-COS3_0 , 1); + + // pass 1 + BF( 1, 30, COS0_1 , 1); + BF(14, 17, COS0_14, 3); + // pass 2 + BF( 1, 14, COS1_1 , 1); + BF(17, 30,-COS1_1 , 1); + // pass 1 + BF( 6, 25, COS0_6 , 1); + BF( 9, 22, COS0_9 , 1); + // pass 2 + BF( 6, 9, COS1_6 , 2); + BF(22, 25,-COS1_6 , 2); + // pass 3 + BF( 1, 6, COS2_1 , 1); + BF( 9, 14,-COS2_1 , 1); + BF(17, 22, COS2_1 , 1); + BF(25, 30,-COS2_1 , 1); + + // pass 1 + BF( 2, 29, COS0_2 , 1); + BF(13, 18, COS0_13, 3); + // pass 2 + BF( 2, 13, COS1_2 , 1); + BF(18, 29,-COS1_2 , 1); + // pass 1 + BF( 5, 26, COS0_5 , 1); + BF(10, 21, COS0_10, 1); + // pass 2 + BF( 5, 10, COS1_5 , 2); + BF(21, 26,-COS1_5 , 2); + // pass 3 + BF( 2, 5, COS2_2 , 1); + BF(10, 13,-COS2_2 , 1); + BF(18, 21, COS2_2 , 1); + BF(26, 29,-COS2_2 , 1); + // pass 4 + BF( 1, 2, COS3_1 , 2); + BF( 5, 6,-COS3_1 , 2); + BF( 9, 10, COS3_1 , 2); + BF(13, 14,-COS3_1 , 2); + BF(17, 18, COS3_1 , 2); + BF(21, 22,-COS3_1 , 2); + BF(25, 26, COS3_1 , 2); + BF(29, 30,-COS3_1 , 2); + + // pass 5 + BF1( 0, 1, 2, 3); + BF2( 4, 5, 6, 7); + BF1( 8, 9, 10, 11); + BF2(12, 13, 14, 15); + BF1(16, 17, 18, 19); + BF2(20, 21, 22, 23); + BF1(24, 25, 26, 27); + BF2(28, 29, 30, 31); + + // pass 6 + ADD( 8, 12); + ADD(12, 10); + ADD(10, 14); + ADD(14, 9); + ADD( 9, 13); + ADD(13, 11); + ADD(11, 15); + + out[ 0] = tab[0]; + out[16] = tab[1]; + out[ 8] = tab[2]; + out[24] = tab[3]; + out[ 4] = tab[4]; + out[20] = tab[5]; + out[12] = tab[6]; + out[28] = tab[7]; + out[ 2] = tab[8]; + out[18] = tab[9]; + out[10] = tab[10]; + out[26] = tab[11]; + out[ 6] = tab[12]; + out[22] = tab[13]; + out[14] = tab[14]; + out[30] = tab[15]; + + ADD(24, 28); + ADD(28, 26); + ADD(26, 30); + ADD(30, 25); + ADD(25, 29); + ADD(29, 27); + ADD(27, 31); + + out[ 1] = tab[16] + tab[24]; + out[17] = tab[17] + tab[25]; + out[ 9] = tab[18] + tab[26]; + out[25] = tab[19] + tab[27]; + out[ 5] = tab[20] + tab[28]; + out[21] = tab[21] + tab[29]; + out[13] = tab[22] + tab[30]; + out[29] = tab[23] + tab[31]; + out[ 3] = tab[24] + tab[20]; + out[19] = tab[25] + tab[21]; + out[11] = tab[26] + tab[22]; + out[27] = tab[27] + tab[23]; + out[ 7] = tab[28] + tab[18]; + out[23] = tab[29] + tab[19]; + out[15] = tab[30] + tab[17]; + out[31] = tab[31]; +} + +// 32 sub band synthesis filter. Input: 32 sub band samples, Output: +// 32 samples. +// XXX: optimize by avoiding ring buffer usage +void ff_mpa_synth_filter(int16 *synth_buf_ptr, int *synth_buf_offset, + int16 *window, int *dither_state, + int16 *samples, int incr, + int32 sb_samples[32]) +{ + int16 *synth_buf; + const int16 *w, *w2, *p; + int j, offset; + int16 *samples2; + int32 tmp[32]; + int sum, sum2; + int tmp_s; + + offset = *synth_buf_offset; + synth_buf = synth_buf_ptr + offset; + + dct32(tmp, sb_samples); + for(j = 0; j < 32; j++) { + // NOTE: can cause a loss in precision if very high amplitude sound + if (tmp[j] < (-0x7fff - 1)) + synth_buf[j] = (-0x7fff - 1); + else if (tmp[j] > 0x7fff) + synth_buf[j] = 0x7fff; + else + synth_buf[j] = tmp[j]; + } + + // copy to avoid wrap + memcpy(synth_buf + 512, synth_buf, 32 * sizeof(int16)); + + samples2 = samples + 31 * incr; + w = window; + w2 = window + 31; + + sum = *dither_state; + p = synth_buf + 16; + SUM8(MACS, sum, w, p); + p = synth_buf + 48; + SUM8(MLSS, sum, w + 32, p); + *samples = round_sample(&sum); + samples += incr; + w++; + + // we calculate two samples at the same time to avoid one memory + // access per two sample + for(j = 1; j < 16; j++) { + sum2 = 0; + p = synth_buf + 16 + j; + SUM8P2(sum, MACS, sum2, MLSS, w, w2, p); + p = synth_buf + 48 - j; + SUM8P2(sum, MLSS, sum2, MLSS, w + 32, w2 + 32, p); + + *samples = round_sample(&sum); + samples += incr; + sum += sum2; + *samples2 = round_sample(&sum); + samples2 -= incr; + w++; + w2--; + } + + p = synth_buf + 32; + SUM8(MLSS, sum, w + 32, p); + *samples = round_sample(&sum); + *dither_state= sum; + + offset = (offset - 32) & 511; + *synth_buf_offset = offset; +} + +/** + * parses a vlc code, faster then get_vlc() + * @param bits is the number of bits which will be read at once, must be + * identical to nb_bits in init_vlc() + * @param max_depth is the number of times bits bits must be read to completely + * read the longest vlc code + * = (max_vlc_length + bits - 1) / bits + */ +static int getVlc2(GetBitContext *s, int16 (*table)[2], int bits, int maxDepth) { + int reIndex; + int reCache; + int index; + int code; + int n; + + debug(1, "int getVlc2(GetBitContext *s, int16 (*table)[2], int bits, int maxDepth)"); + + reIndex = s->index; + reCache = READ_LE_UINT32(s->buffer + (reIndex >> 3)) >> (reIndex & 0x07); + index = reCache & (0xffffffff >> (32 - bits)); + code = table[index][0]; + n = table[index][1]; + + debug(1, "reIndex : %d", reIndex); + debug(1, "reCache : %d", reCache); + debug(1, "index : %d", index); + debug(1, "code : %d", code); + debug(1, "n : %d", n); + + if (maxDepth > 1 && n < 0){ + reIndex += bits; + reCache = READ_LE_UINT32(s->buffer + (reIndex >> 3)) >> (reIndex & 0x07); + + int nbBits = -n; + + index = (reCache & (0xffffffff >> (32 - nbBits))) + code; + code = table[index][0]; + n = table[index][1]; + + if(maxDepth > 2 && n < 0) { + reIndex += nbBits; + reCache = READ_LE_UINT32(s->buffer + (reIndex >> 3)) >> (reIndex & 0x07); + + nbBits = -n; + + index = (reCache & (0xffffffff >> (32 - nbBits))) + code; + code = table[index][0]; + n = table[index][1]; + } + } + + reCache >>= n; + s->index = reIndex + n; + return code; +} + +static int allocTable(VLC *vlc, int size, int use_static) { + int index; + index = vlc->table_size; + vlc->table_size += size; + if (vlc->table_size > vlc->table_allocated) { + if(use_static) + error("QDM2 cant do anything, init_vlc() is used with too little memory"); + vlc->table_allocated += (1 << vlc->bits); + vlc->table = (int16 (*)[2])realloc(vlc->table, sizeof(int16 *) * 2 * vlc->table_allocated); + if (!vlc->table) + return -1; + } + return index; +} + +#define GET_DATA(v, table, i, wrap, size)\ +{\ + const uint8 *ptr = (const uint8 *)table + i * wrap;\ + switch(size) {\ + case 1:\ + v = *(const uint8 *)ptr;\ + break;\ + case 2:\ + v = *(const uint16 *)ptr;\ + break;\ + default:\ + v = *(const uint32 *)ptr;\ + break;\ + }\ +} + +static int build_table(VLC *vlc, int table_nb_bits, + int nb_codes, + const void *bits, int bits_wrap, int bits_size, + const void *codes, int codes_wrap, int codes_size, + const void *symbols, int symbols_wrap, int symbols_size, + int code_prefix, int n_prefix, int flags) +{ + int i, j, k, n, table_size, table_index, nb, n1, index, code_prefix2, symbol; + uint32 code; + int16 (*table)[2]; + + table_size = 1 << table_nb_bits; + table_index = allocTable(vlc, table_size, flags & 4); + debug(2, "QDM2 new table index=%d size=%d code_prefix=%x n=%d", table_index, table_size, code_prefix, n_prefix); + if (table_index < 0) + return -1; + table = &vlc->table[table_index]; + + for(i = 0; i < table_size; i++) { + table[i][1] = 0; //bits + table[i][0] = -1; //codes + } + + // first pass: map codes and compute auxillary table sizes + for(i = 0; i < nb_codes; i++) { + GET_DATA(n, bits, i, bits_wrap, bits_size); + GET_DATA(code, codes, i, codes_wrap, codes_size); + // we accept tables with holes + if (n <= 0) + continue; + if (!symbols) + symbol = i; + else + GET_DATA(symbol, symbols, i, symbols_wrap, symbols_size); + debug(2, "QDM2 i=%d n=%d code=0x%x", i, n, code); + // if code matches the prefix, it is in the table + n -= n_prefix; + if(flags & 2) + code_prefix2= code & (n_prefix>=32 ? 0xffffffff : (1 << n_prefix)-1); + else + code_prefix2= code >> n; + if (n > 0 && code_prefix2 == code_prefix) { + if (n <= table_nb_bits) { + // no need to add another table + j = (code << (table_nb_bits - n)) & (table_size - 1); + nb = 1 << (table_nb_bits - n); + for(k = 0; k < nb; k++) { + if(flags & 2) + j = (code >> n_prefix) + (k<<n); + debug(2, "QDM2 %4x: code=%d n=%d",j, i, n); + if (table[j][1] /*bits*/ != 0) { + error("QDM2 incorrect codes"); + return -1; + } + table[j][1] = n; //bits + table[j][0] = symbol; + j++; + } + } else { + n -= table_nb_bits; + j = (code >> ((flags & 2) ? n_prefix : n)) & ((1 << table_nb_bits) - 1); + debug(2, "QDM2 %4x: n=%d (subtable)", j, n); + // compute table size + n1 = -table[j][1]; //bits + if (n > n1) + n1 = n; + table[j][1] = -n1; //bits + } + } + } + + // second pass : fill auxillary tables recursively + for(i = 0;i < table_size; i++) { + n = table[i][1]; //bits + if (n < 0) { + n = -n; + if (n > table_nb_bits) { + n = table_nb_bits; + table[i][1] = -n; //bits + } + index = build_table(vlc, n, nb_codes, + bits, bits_wrap, bits_size, + codes, codes_wrap, codes_size, + symbols, symbols_wrap, symbols_size, + (flags & 2) ? (code_prefix | (i << n_prefix)) : ((code_prefix << table_nb_bits) | i), + n_prefix + table_nb_bits, flags); + if (index < 0) + return -1; + // note: realloc has been done, so reload tables + table = &vlc->table[table_index]; + table[i][0] = index; //code + } + } + return table_index; +} + +/* Build VLC decoding tables suitable for use with get_vlc(). + + 'nb_bits' set thee decoding table size (2^nb_bits) entries. The + bigger it is, the faster is the decoding. But it should not be too + big to save memory and L1 cache. '9' is a good compromise. + + 'nb_codes' : number of vlcs codes + + 'bits' : table which gives the size (in bits) of each vlc code. + + 'codes' : table which gives the bit pattern of of each vlc code. + + 'symbols' : table which gives the values to be returned from get_vlc(). + + 'xxx_wrap' : give the number of bytes between each entry of the + 'bits' or 'codes' tables. + + 'xxx_size' : gives the number of bytes of each entry of the 'bits' + or 'codes' tables. + + 'wrap' and 'size' allows to use any memory configuration and types + (byte/word/long) to store the 'bits', 'codes', and 'symbols' tables. + + 'use_static' should be set to 1 for tables, which should be freed + with av_free_static(), 0 if free_vlc() will be used. +*/ +void initVlcSparse(VLC *vlc, int nb_bits, int nb_codes, + const void *bits, int bits_wrap, int bits_size, + const void *codes, int codes_wrap, int codes_size, + const void *symbols, int symbols_wrap, int symbols_size) { + vlc->bits = nb_bits; + + if(vlc->table_size && vlc->table_size == vlc->table_allocated) { + return; + } else if(vlc->table_size) { + error("called on a partially initialized table"); + } + + debug(2, "QDM2 build table nb_codes=%d", nb_codes); + + if (build_table(vlc, nb_bits, nb_codes, + bits, bits_wrap, bits_size, + codes, codes_wrap, codes_size, + symbols, symbols_wrap, symbols_size, + 0, 0, 4 | 2) < 0) { + free(&vlc->table); + return; // Error + } + + if(vlc->table_size != vlc->table_allocated) + error("QDM2 needed %d had %d", vlc->table_size, vlc->table_allocated); +} + +void QDM2Stream::softclipTableInit(void) { + uint16 i; + double dfl = SOFTCLIP_THRESHOLD - 32767; + float delta = 1.0 / -dfl; + + for (i = 0; i < ARRAYSIZE(_softclipTable); i++) + _softclipTable[i] = SOFTCLIP_THRESHOLD - ((int)(sin((float)i * delta) * dfl) & 0x0000FFFF); +} + +// random generated table +void QDM2Stream::rndTableInit(void) { + uint16 i; + uint16 j; + uint32 ldw, hdw; + // TODO: Replace Code with uint64 less version... + int64_t tmp64_1; + int64_t random_seed = 0; + float delta = 1.0 / 16384.0; + + for(i = 0; i < ARRAYSIZE(_noiseTable); i++) { + random_seed = random_seed * 214013 + 2531011; + _noiseTable[i] = (delta * (float)(((int32)random_seed >> 16) & 0x00007FFF)- 1.0) * 1.3; + } + + for (i = 0; i < 256; i++) { + random_seed = 81; + ldw = i; + for (j = 0; j < 5; j++) { + _randomDequantIndex[i][j] = (uint8)((ldw / random_seed) & 0xFF); + ldw = (uint32)ldw % (uint32)random_seed; + tmp64_1 = (random_seed * 0x55555556); + hdw = (uint32)(tmp64_1 >> 32); + random_seed = (int64_t)(hdw + (ldw >> 31)); + } + } + + for (i = 0; i < 128; i++) { + random_seed = 25; + ldw = i; + for (j = 0; j < 3; j++) { + _randomDequantType24[i][j] = (uint8)((ldw / random_seed) & 0xFF); + ldw = (uint32)ldw % (uint32)random_seed; + tmp64_1 = (random_seed * 0x66666667); + hdw = (uint32)(tmp64_1 >> 33); + random_seed = hdw + (ldw >> 31); + } + } +} + +void QDM2Stream::initNoiseSamples(void) { + uint16 i; + uint32 random_seed = 0; + float delta = 1.0 / 16384.0; + + for (i = 0; i < ARRAYSIZE(_noiseSamples); i++) { + random_seed = random_seed * 214013 + 2531011; + _noiseSamples[i] = (delta * (float)((random_seed >> 16) & 0x00007fff) - 1.0); + } +} + +static const uint16 qdm2_vlc_offs[18] = { + 0, 260, 566, 598, 894, 1166, 1230, 1294, 1678, 1950, 2214, 2278, 2310, 2570, 2834, 3124, 3448, 3838 +}; + +void QDM2Stream::initVlc(void) { + static int16 qdm2_table[3838][2]; + + if (!_vlcsInitialized) { + _vlcTabLevel.table = &qdm2_table[qdm2_vlc_offs[0]]; + _vlcTabLevel.table_allocated = qdm2_vlc_offs[1] - qdm2_vlc_offs[0]; + _vlcTabLevel.table_size = 0; + initVlcSparse(&_vlcTabLevel, 8, 24, + vlc_tab_level_huffbits, 1, 1, + vlc_tab_level_huffcodes, 2, 2, NULL, 0, 0); + + _vlcTabDiff.table = &qdm2_table[qdm2_vlc_offs[1]]; + _vlcTabDiff.table_allocated = qdm2_vlc_offs[2] - qdm2_vlc_offs[1]; + _vlcTabDiff.table_size = 0; + initVlcSparse(&_vlcTabDiff, 8, 37, + vlc_tab_diff_huffbits, 1, 1, + vlc_tab_diff_huffcodes, 2, 2, NULL, 0, 0); + + _vlcTabRun.table = &qdm2_table[qdm2_vlc_offs[2]]; + _vlcTabRun.table_allocated = qdm2_vlc_offs[3] - qdm2_vlc_offs[2]; + _vlcTabRun.table_size = 0; + initVlcSparse(&_vlcTabRun, 5, 6, + vlc_tab_run_huffbits, 1, 1, + vlc_tab_run_huffcodes, 1, 1, NULL, 0, 0); + + _fftLevelExpAltVlc.table = &qdm2_table[qdm2_vlc_offs[3]]; + _fftLevelExpAltVlc.table_allocated = qdm2_vlc_offs[4] - qdm2_vlc_offs[3]; + _fftLevelExpAltVlc.table_size = 0; + initVlcSparse(&_fftLevelExpAltVlc, 8, 28, + fft_level_exp_alt_huffbits, 1, 1, + fft_level_exp_alt_huffcodes, 2, 2, NULL, 0, 0); + + _fftLevelExpVlc.table = &qdm2_table[qdm2_vlc_offs[4]]; + _fftLevelExpVlc.table_allocated = qdm2_vlc_offs[5] - qdm2_vlc_offs[4]; + _fftLevelExpVlc.table_size = 0; + initVlcSparse(&_fftLevelExpVlc, 8, 20, + fft_level_exp_huffbits, 1, 1, + fft_level_exp_huffcodes, 2, 2, NULL, 0, 0); + + _fftStereoExpVlc.table = &qdm2_table[qdm2_vlc_offs[5]]; + _fftStereoExpVlc.table_allocated = qdm2_vlc_offs[6] - qdm2_vlc_offs[5]; + _fftStereoExpVlc.table_size = 0; + initVlcSparse(&_fftStereoExpVlc, 6, 7, + fft_stereo_exp_huffbits, 1, 1, + fft_stereo_exp_huffcodes, 1, 1, NULL, 0, 0); + + _fftStereoPhaseVlc.table = &qdm2_table[qdm2_vlc_offs[6]]; + _fftStereoPhaseVlc.table_allocated = qdm2_vlc_offs[7] - qdm2_vlc_offs[6]; + _fftStereoPhaseVlc.table_size = 0; + initVlcSparse(&_fftStereoPhaseVlc, 6, 9, + fft_stereo_phase_huffbits, 1, 1, + fft_stereo_phase_huffcodes, 1, 1, NULL, 0, 0); + + _vlcTabToneLevelIdxHi1.table = &qdm2_table[qdm2_vlc_offs[7]]; + _vlcTabToneLevelIdxHi1.table_allocated = qdm2_vlc_offs[8] - qdm2_vlc_offs[7]; + _vlcTabToneLevelIdxHi1.table_size = 0; + initVlcSparse(&_vlcTabToneLevelIdxHi1, 8, 20, + vlc_tab_tone_level_idx_hi1_huffbits, 1, 1, + vlc_tab_tone_level_idx_hi1_huffcodes, 2, 2, NULL, 0, 0); + + _vlcTabToneLevelIdxMid.table = &qdm2_table[qdm2_vlc_offs[8]]; + _vlcTabToneLevelIdxMid.table_allocated = qdm2_vlc_offs[9] - qdm2_vlc_offs[8]; + _vlcTabToneLevelIdxMid.table_size = 0; + initVlcSparse(&_vlcTabToneLevelIdxMid, 8, 24, + vlc_tab_tone_level_idx_mid_huffbits, 1, 1, + vlc_tab_tone_level_idx_mid_huffcodes, 2, 2, NULL, 0, 0); + + _vlcTabToneLevelIdxHi2.table = &qdm2_table[qdm2_vlc_offs[9]]; + _vlcTabToneLevelIdxHi2.table_allocated = qdm2_vlc_offs[10] - qdm2_vlc_offs[9]; + _vlcTabToneLevelIdxHi2.table_size = 0; + initVlcSparse(&_vlcTabToneLevelIdxHi2, 8, 24, + vlc_tab_tone_level_idx_hi2_huffbits, 1, 1, + vlc_tab_tone_level_idx_hi2_huffcodes, 2, 2, NULL, 0, 0); + + _vlcTabType30.table = &qdm2_table[qdm2_vlc_offs[10]]; + _vlcTabType30.table_allocated = qdm2_vlc_offs[11] - qdm2_vlc_offs[10]; + _vlcTabType30.table_size = 0; + initVlcSparse(&_vlcTabType30, 6, 9, + vlc_tab_type30_huffbits, 1, 1, + vlc_tab_type30_huffcodes, 1, 1, NULL, 0, 0); + + _vlcTabType34.table = &qdm2_table[qdm2_vlc_offs[11]]; + _vlcTabType34.table_allocated = qdm2_vlc_offs[12] - qdm2_vlc_offs[11]; + _vlcTabType34.table_size = 0; + initVlcSparse(&_vlcTabType34, 5, 10, + vlc_tab_type34_huffbits, 1, 1, + vlc_tab_type34_huffcodes, 1, 1, NULL, 0, 0); + + _vlcTabFftToneOffset[0].table = &qdm2_table[qdm2_vlc_offs[12]]; + _vlcTabFftToneOffset[0].table_allocated = qdm2_vlc_offs[13] - qdm2_vlc_offs[12]; + _vlcTabFftToneOffset[0].table_size = 0; + initVlcSparse(&_vlcTabFftToneOffset[0], 8, 23, + vlc_tab_fft_tone_offset_0_huffbits, 1, 1, + vlc_tab_fft_tone_offset_0_huffcodes, 2, 2, NULL, 0, 0); + + _vlcTabFftToneOffset[1].table = &qdm2_table[qdm2_vlc_offs[13]]; + _vlcTabFftToneOffset[1].table_allocated = qdm2_vlc_offs[14] - qdm2_vlc_offs[13]; + _vlcTabFftToneOffset[1].table_size = 0; + initVlcSparse(&_vlcTabFftToneOffset[1], 8, 28, + vlc_tab_fft_tone_offset_1_huffbits, 1, 1, + vlc_tab_fft_tone_offset_1_huffcodes, 2, 2, NULL, 0, 0); + + _vlcTabFftToneOffset[2].table = &qdm2_table[qdm2_vlc_offs[14]]; + _vlcTabFftToneOffset[2].table_allocated = qdm2_vlc_offs[15] - qdm2_vlc_offs[14]; + _vlcTabFftToneOffset[2].table_size = 0; + initVlcSparse(&_vlcTabFftToneOffset[2], 8, 32, + vlc_tab_fft_tone_offset_2_huffbits, 1, 1, + vlc_tab_fft_tone_offset_2_huffcodes, 2, 2, NULL, 0, 0); + + _vlcTabFftToneOffset[3].table = &qdm2_table[qdm2_vlc_offs[15]]; + _vlcTabFftToneOffset[3].table_allocated = qdm2_vlc_offs[16] - qdm2_vlc_offs[15]; + _vlcTabFftToneOffset[3].table_size = 0; + initVlcSparse(&_vlcTabFftToneOffset[3], 8, 35, + vlc_tab_fft_tone_offset_3_huffbits, 1, 1, + vlc_tab_fft_tone_offset_3_huffcodes, 2, 2, NULL, 0, 0); + + _vlcTabFftToneOffset[4].table = &qdm2_table[qdm2_vlc_offs[16]]; + _vlcTabFftToneOffset[4].table_allocated = qdm2_vlc_offs[17] - qdm2_vlc_offs[16]; + _vlcTabFftToneOffset[4].table_size = 0; + initVlcSparse(&_vlcTabFftToneOffset[4], 8, 38, + vlc_tab_fft_tone_offset_4_huffbits, 1, 1, + vlc_tab_fft_tone_offset_4_huffcodes, 2, 2, NULL, 0, 0); + + _vlcsInitialized = true; + } +} + +QDM2Stream::QDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadStream *extraData) { + uint32 tmp; + int32 tmp_s; + int tmp_val; + int i; + + debug(1, "QDM2Stream::QDM2Stream() Call"); + + _stream = stream; + _compressedData = NULL; + _subPacket = 0; + memset(_quantizedCoeffs, 0, sizeof(_quantizedCoeffs)); + memset(_fftLevelExp, 0, sizeof(_fftLevelExp)); + _noiseIdx = 0; + memset(_fftCoefsMinIndex, 0, sizeof(_fftCoefsMinIndex)); + memset(_fftCoefsMaxIndex, 0, sizeof(_fftCoefsMaxIndex)); + _fftToneStart = 0; + _fftToneEnd = 0; + for(i = 0; i < ARRAYSIZE(_subPacketListA); i++) { + _subPacketListA[i].packet = NULL; + _subPacketListA[i].next = NULL; + } + _subPacketsB = 0; + for(i = 0; i < ARRAYSIZE(_subPacketListB); i++) { + _subPacketListB[i].packet = NULL; + _subPacketListB[i].next = NULL; + } + for(i = 0; i < ARRAYSIZE(_subPacketListC); i++) { + _subPacketListC[i].packet = NULL; + _subPacketListC[i].next = NULL; + } + for(i = 0; i < ARRAYSIZE(_subPacketListD); i++) { + _subPacketListD[i].packet = NULL; + _subPacketListD[i].next = NULL; + } + memset(_synthBuf, 0, sizeof(_synthBuf)); + memset(_synthBufOffset, 0, sizeof(_synthBufOffset)); + memset(_sbSamples, 0, sizeof(_sbSamples)); + memset(_outputBuffer, 0, sizeof(_outputBuffer)); + _vlcsInitialized = false; + _superblocktype_2_3 = 0; + _hasErrors = false; + + // Rewind extraData stream from any previous calls... + extraData->seek(0, SEEK_SET); + + tmp_s = extraData->readSint32BE(); + debug(1, "QDM2Stream::QDM2Stream() extraSize: %d", tmp_s); + if ((extraData->size() - extraData->pos()) / 4 + 1 != tmp_s) + warning("QDM2Stream::QDM2Stream() extraSize mismatch - Expected %d", (extraData->size() - extraData->pos()) / 4 + 1); + if (tmp_s < 12) + error("QDM2Stream::QDM2Stream() Insufficient extraData"); + + tmp = extraData->readUint32BE(); + debug(1, "QDM2Stream::QDM2Stream() extraTag: %d", tmp); + if (tmp != MKID_BE('frma')) + warning("QDM2Stream::QDM2Stream() extraTag mismatch"); + + tmp = extraData->readUint32BE(); + debug(1, "QDM2Stream::QDM2Stream() extraType: %d", tmp); + if (tmp == MKID_BE('QDMC')) + warning("QDM2Stream::QDM2Stream() QDMC stream type not supported."); + else if (tmp != MKID_BE('QDM2')) + error("QDM2Stream::QDM2Stream() Unsupported stream type"); + + tmp_s = extraData->readSint32BE(); + debug(1, "QDM2Stream::QDM2Stream() extraSize2: %d", tmp_s); + if ((extraData->size() - extraData->pos()) + 4 != tmp_s) + warning("QDM2Stream::QDM2Stream() extraSize2 mismatch - Expected %d", (extraData->size() - extraData->pos()) + 4); + + tmp = extraData->readUint32BE(); + debug(1, "QDM2Stream::QDM2Stream() extraTag2: %d", tmp); + if (tmp != MKID_BE('QDCA')) + warning("QDM2Stream::QDM2Stream() extraTag2 mismatch"); + + if (extraData->readUint32BE() != 1) + warning("QDM2Stream::QDM2Stream() u0 field not 1"); + + _channels = extraData->readUint32BE(); + debug(1, "QDM2Stream::QDM2Stream() channels: %d", _channels); + + _sampleRate = extraData->readUint32BE(); + debug(1, "QDM2Stream::QDM2Stream() sampleRate: %d", _sampleRate); + + _bitRate = extraData->readUint32BE(); + debug(1, "QDM2Stream::QDM2Stream() bitRate: %d", _bitRate); + + _blockSize = extraData->readUint32BE(); + debug(1, "QDM2Stream::QDM2Stream() blockSize: %d", _blockSize); + + _frameSize = extraData->readUint32BE(); + debug(1, "QDM2Stream::QDM2Stream() frameSize: %d", _frameSize); + + _packetSize = extraData->readUint32BE(); + debug(1, "QDM2Stream::QDM2Stream() packetSize: %d", _packetSize); + + if (extraData->size() - extraData->pos() != 0) { + tmp_s = extraData->readSint32BE(); + debug(1, "QDM2Stream::QDM2Stream() extraSize3: %d", tmp_s); + if (extraData->size() + 4 != tmp_s) + warning("QDM2Stream::QDM2Stream() extraSize3 mismatch - Expected %d", extraData->size() + 4); + + tmp = extraData->readUint32BE(); + debug(1, "QDM2Stream::QDM2Stream() extraTag3: %d", tmp); + if (tmp != MKID_BE('QDCP')) + warning("QDM2Stream::QDM2Stream() extraTag3 mismatch"); + + if ((float)extraData->readUint32BE() != 1.0) + warning("QDM2Stream::QDM2Stream() uf0 field not 1.0"); + + if (extraData->readUint32BE() != 0) + warning("QDM2Stream::QDM2Stream() u1 field not 0"); + + if ((float)extraData->readUint32BE() != 1.0) + warning("QDM2Stream::QDM2Stream() uf1 field not 1.0"); + + if ((float)extraData->readUint32BE() != 1.0) + warning("QDM2Stream::QDM2Stream() uf2 field not 1.0"); + + if (extraData->readUint32BE() != 27) + warning("QDM2Stream::QDM2Stream() u2 field not 27"); + + if (extraData->readUint32BE() != 8) + warning("QDM2Stream::QDM2Stream() u3 field not 8"); + + if (extraData->readUint32BE() != 0) + warning("QDM2Stream::QDM2Stream() u4 field not 0"); + } + + _fftOrder = scummvm_log2(_frameSize) + 1; + _fftFrameSize = 2 * _frameSize; // complex has two floats + + // something like max decodable tones + _groupOrder = scummvm_log2(_blockSize) + 1; + _sFrameSize = _blockSize / 16; // 16 iterations per super block + + _subSampling = _fftOrder - 7; + _frequencyRange = 255 / (1 << (2 - _subSampling)); + + switch ((_subSampling * 2 + _channels - 1)) { + case 0: + tmp = 40; + break; + case 1: + tmp = 48; + break; + case 2: + tmp = 56; + break; + case 3: + tmp = 72; + break; + case 4: + tmp = 80; + break; + case 5: + tmp = 100; + break; + default: + tmp = _subSampling; + break; + } + + tmp_val = 0; + if ((tmp * 1000) < _bitRate) tmp_val = 1; + if ((tmp * 1440) < _bitRate) tmp_val = 2; + if ((tmp * 1760) < _bitRate) tmp_val = 3; + if ((tmp * 2240) < _bitRate) tmp_val = 4; + _cmTableSelect = tmp_val; + + if (_subSampling == 0) + tmp = 7999; + else + tmp = ((-(_subSampling -1)) & 8000) + 20000; + + if (tmp < 8000) + _coeffPerSbSelect = 0; + else if (tmp <= 16000) + _coeffPerSbSelect = 1; + else + _coeffPerSbSelect = 2; + + if (_fftOrder < 7 || _fftOrder > 9) + error("QDM2Stream::QDM2Stream() Unsupported fft_order: %d", _fftOrder); + + rdftInit(&_rdftCtx, _fftOrder, IRDFT); + + initVlc(); + ff_mpa_synth_init(ff_mpa_synth_window); + softclipTableInit(); + rndTableInit(); + initNoiseSamples(); + + _compressedData = new uint8[_packetSize]; +} + +QDM2Stream::~QDM2Stream() { + delete[] _compressedData; + delete _stream; +} + +static int qdm2_get_vlc(GetBitContext *gb, VLC *vlc, int flag, int depth) { + int value = getVlc2(gb, vlc->table, vlc->bits, depth); + + // stage-2, 3 bits exponent escape sequence + if (value-- == 0) + value = getBits(gb, getBits (gb, 3) + 1); + + // stage-3, optional + if (flag) { + int tmp = vlc_stage3_values[value]; + + if ((value & ~3) > 0) + tmp += getBits(gb, (value >> 2)); + value = tmp; + } + + return value; +} + +static int qdm2_get_se_vlc(VLC *vlc, GetBitContext *gb, int depth) +{ + int value = qdm2_get_vlc(gb, vlc, 0, depth); + + return (value & 1) ? ((value + 1) >> 1) : -(value >> 1); +} + +/** + * QDM2 checksum + * + * @param data pointer to data to be checksum'ed + * @param length data length + * @param value checksum value + * + * @return 0 if checksum is OK + */ +static uint16 qdm2_packet_checksum(const uint8 *data, int length, int value) { + int i; + + for (i = 0; i < length; i++) + value -= data[i]; + + return (uint16)(value & 0xffff); +} + +/** + * Fills a QDM2SubPacket structure with packet type, size, and data pointer. + * + * @param gb bitreader context + * @param sub_packet packet under analysis + */ +static void qdm2_decode_sub_packet_header(GetBitContext *gb, QDM2SubPacket *sub_packet) +{ + sub_packet->type = getBits (gb, 8); + + if (sub_packet->type == 0) { + sub_packet->size = 0; + sub_packet->data = NULL; + } else { + sub_packet->size = getBits (gb, 8); + + if (sub_packet->type & 0x80) { + sub_packet->size <<= 8; + sub_packet->size |= getBits (gb, 8); + sub_packet->type &= 0x7f; + } + + if (sub_packet->type == 0x7f) + sub_packet->type |= (getBits (gb, 8) << 8); + + sub_packet->data = &gb->buffer[getBitsCount(gb) / 8]; // FIXME: this depends on bitreader internal data + } + + debug(1, "QDM2 Subpacket: type=%d size=%d start_offs=%x", sub_packet->type, sub_packet->size, getBitsCount(gb) / 8); +} + +/** + * Return node pointer to first packet of requested type in list. + * + * @param list list of subpackets to be scanned + * @param type type of searched subpacket + * @return node pointer for subpacket if found, else NULL + */ +static QDM2SubPNode* qdm2_search_subpacket_type_in_list(QDM2SubPNode *list, int type) +{ + while (list != NULL && list->packet != NULL) { + if (list->packet->type == type) + return list; + list = list->next; + } + return NULL; +} + +/** + * Replaces 8 elements with their average value. + * Called by qdm2_decode_superblock before starting subblock decoding. + */ +void QDM2Stream::average_quantized_coeffs(void) { + int i, j, n, ch, sum; + + n = coeff_per_sb_for_avg[_coeffPerSbSelect][QDM2_SB_USED(_subSampling) - 1] + 1; + + for (ch = 0; ch < _channels; ch++) { + for (i = 0; i < n; i++) { + sum = 0; + + for (j = 0; j < 8; j++) + sum += _quantizedCoeffs[ch][i][j]; + + sum /= 8; + if (sum > 0) + sum--; + + for (j = 0; j < 8; j++) + _quantizedCoeffs[ch][i][j] = sum; + } + } +} + +/** + * Build subband samples with noise weighted by q->tone_level. + * Called by synthfilt_build_sb_samples. + * + * @param sb subband index + */ +void QDM2Stream::build_sb_samples_from_noise(int sb) { + int ch, j; + + FIX_NOISE_IDX(_noiseIdx); + + if (!_channels) + return; + + for (ch = 0; ch < _channels; ch++) { + for (j = 0; j < 64; j++) { + _sbSamples[ch][j * 2][sb] = (int32)(SB_DITHERING_NOISE(sb, _noiseIdx) * _toneLevel[ch][sb][j] + .5); + _sbSamples[ch][j * 2 + 1][sb] = (int32)(SB_DITHERING_NOISE(sb, _noiseIdx) * _toneLevel[ch][sb][j] + .5); + } + } +} + +/** + * Called while processing data from subpackets 11 and 12. + * Used after making changes to coding_method array. + * + * @param sb subband index + * @param channels number of channels + * @param coding_method q->coding_method[0][0][0] + */ +void QDM2Stream::fix_coding_method_array(int sb, int channels, sb_int8_array coding_method) +{ + int j, k; + int ch; + int run, case_val; + int switchtable[23] = {0,5,1,5,5,5,5,5,2,5,5,5,5,5,5,5,3,5,5,5,5,5,4}; + + for (ch = 0; ch < channels; ch++) { + for (j = 0; j < 64; ) { + if((coding_method[ch][sb][j] - 8) > 22) { + run = 1; + case_val = 8; + } else { + switch (switchtable[coding_method[ch][sb][j]-8]) { + case 0: run = 10; case_val = 10; break; + case 1: run = 1; case_val = 16; break; + case 2: run = 5; case_val = 24; break; + case 3: run = 3; case_val = 30; break; + case 4: run = 1; case_val = 30; break; + case 5: run = 1; case_val = 8; break; + default: run = 1; case_val = 8; break; + } + } + for (k = 0; k < run; k++) + if (j + k < 128) + if (coding_method[ch][sb + (j + k) / 64][(j + k) % 64] > coding_method[ch][sb][j]) + if (k > 0) { + warning("QDM2 Untested Code: not debugged, almost never used"); + memset(&coding_method[ch][sb][j + k], case_val, k * sizeof(int8)); + memset(&coding_method[ch][sb][j + k], case_val, 3 * sizeof(int8)); + } + j += run; + } + } +} + +/** + * Related to synthesis filter + * Called by process_subpacket_10 + * + * @param flag 1 if called after getting data from subpacket 10, 0 if no subpacket 10 + */ +void QDM2Stream::fill_tone_level_array(int flag) { + int i, sb, ch, sb_used; + int tmp, tab; + + // This should never happen + if (_channels <= 0) + return; + + for (ch = 0; ch < _channels; ch++) { + for (sb = 0; sb < 30; sb++) { + for (i = 0; i < 8; i++) { + if ((tab=coeff_per_sb_for_dequant[_coeffPerSbSelect][sb]) < (last_coeff[_coeffPerSbSelect] - 1)) + tmp = _quantizedCoeffs[ch][tab + 1][i] * dequant_table[_coeffPerSbSelect][tab + 1][sb]+ + _quantizedCoeffs[ch][tab][i] * dequant_table[_coeffPerSbSelect][tab][sb]; + else + tmp = _quantizedCoeffs[ch][tab][i] * dequant_table[_coeffPerSbSelect][tab][sb]; + if(tmp < 0) + tmp += 0xff; + _toneLevelIdxBase[ch][sb][i] = (tmp / 256) & 0xff; + } + } + } + + sb_used = QDM2_SB_USED(_subSampling); + + if ((_superblocktype_2_3 != 0) && !flag) { + for (sb = 0; sb < sb_used; sb++) { + for (ch = 0; ch < _channels; ch++) { + for (i = 0; i < 64; i++) { + _toneLevelIdx[ch][sb][i] = _toneLevelIdxBase[ch][sb][i / 8]; + if (_toneLevelIdx[ch][sb][i] < 0) + _toneLevel[ch][sb][i] = 0; + else + _toneLevel[ch][sb][i] = fft_tone_level_table[0][_toneLevelIdx[ch][sb][i] & 0x3f]; + } + } + } + } else { + tab = _superblocktype_2_3 ? 0 : 1; + for (sb = 0; sb < sb_used; sb++) { + if ((sb >= 4) && (sb <= 23)) { + for (ch = 0; ch < _channels; ch++) { + for (i = 0; i < 64; i++) { + tmp = _toneLevelIdxBase[ch][sb][i / 8] - + _toneLevelIdxHi1[ch][sb / 8][i / 8][i % 8] - + _toneLevelIdxMid[ch][sb - 4][i / 8] - + _toneLevelIdxHi2[ch][sb - 4]; + _toneLevelIdx[ch][sb][i] = tmp & 0xff; + if ((tmp < 0) || (!_superblocktype_2_3 && !tmp)) + _toneLevel[ch][sb][i] = 0; + else + _toneLevel[ch][sb][i] = fft_tone_level_table[tab][tmp & 0x3f]; + } + } + } else { + if (sb > 4) { + for (ch = 0; ch < _channels; ch++) { + for (i = 0; i < 64; i++) { + tmp = _toneLevelIdxBase[ch][sb][i / 8] - + _toneLevelIdxHi1[ch][2][i / 8][i % 8] - + _toneLevelIdxHi2[ch][sb - 4]; + _toneLevelIdx[ch][sb][i] = tmp & 0xff; + if ((tmp < 0) || (!_superblocktype_2_3 && !tmp)) + _toneLevel[ch][sb][i] = 0; + else + _toneLevel[ch][sb][i] = fft_tone_level_table[tab][tmp & 0x3f]; + } + } + } else { + for (ch = 0; ch < _channels; ch++) { + for (i = 0; i < 64; i++) { + tmp = _toneLevelIdx[ch][sb][i] = _toneLevelIdxBase[ch][sb][i / 8]; + if ((tmp < 0) || (!_superblocktype_2_3 && !tmp)) + _toneLevel[ch][sb][i] = 0; + else + _toneLevel[ch][sb][i] = fft_tone_level_table[tab][tmp & 0x3f]; + } + } + } + } + } + } +} + +/** + * Related to synthesis filter + * Called by process_subpacket_11 + * c is built with data from subpacket 11 + * Most of this function is used only if superblock_type_2_3 == 0, never seen it in samples + * + * @param tone_level_idx + * @param tone_level_idx_temp + * @param coding_method q->coding_method[0][0][0] + * @param nb_channels number of channels + * @param c coming from subpacket 11, passed as 8*c + * @param superblocktype_2_3 flag based on superblock packet type + * @param cm_table_select q->cm_table_select + */ +void QDM2Stream::fill_coding_method_array(sb_int8_array tone_level_idx, sb_int8_array tone_level_idx_temp, + sb_int8_array coding_method, int nb_channels, + int c, int superblocktype_2_3, int cm_table_select) { + int ch, sb, j; + int tmp, acc, esp_40, comp; + int add1, add2, add3, add4; + // TODO : Remove multres 64 bit variable necessity... + int64_t multres; + + // This should never happen + if (nb_channels <= 0) + return; + if (!superblocktype_2_3) { + warning("QDM2 This case is untested, no samples available"); + for (ch = 0; ch < nb_channels; ch++) + for (sb = 0; sb < 30; sb++) { + for (j = 1; j < 63; j++) { // The loop only iterates to 63 so the code doesn't overflow the buffer + add1 = tone_level_idx[ch][sb][j] - 10; + if (add1 < 0) + add1 = 0; + add2 = add3 = add4 = 0; + if (sb > 1) { + add2 = tone_level_idx[ch][sb - 2][j] + tone_level_idx_offset_table[sb][0] - 6; + if (add2 < 0) + add2 = 0; + } + if (sb > 0) { + add3 = tone_level_idx[ch][sb - 1][j] + tone_level_idx_offset_table[sb][1] - 6; + if (add3 < 0) + add3 = 0; + } + if (sb < 29) { + add4 = tone_level_idx[ch][sb + 1][j] + tone_level_idx_offset_table[sb][3] - 6; + if (add4 < 0) + add4 = 0; + } + tmp = tone_level_idx[ch][sb][j + 1] * 2 - add4 - add3 - add2 - add1; + if (tmp < 0) + tmp = 0; + tone_level_idx_temp[ch][sb][j + 1] = tmp & 0xff; + } + tone_level_idx_temp[ch][sb][0] = tone_level_idx_temp[ch][sb][1]; + } + acc = 0; + for (ch = 0; ch < nb_channels; ch++) + for (sb = 0; sb < 30; sb++) + for (j = 0; j < 64; j++) + acc += tone_level_idx_temp[ch][sb][j]; + + multres = 0x66666667 * (acc * 10); + esp_40 = (multres >> 32) / 8 + ((multres & 0xffffffff) >> 31); + for (ch = 0; ch < nb_channels; ch++) + for (sb = 0; sb < 30; sb++) + for (j = 0; j < 64; j++) { + comp = tone_level_idx_temp[ch][sb][j]* esp_40 * 10; + if (comp < 0) + comp += 0xff; + comp /= 256; // signed shift + switch(sb) { + case 0: + if (comp < 30) + comp = 30; + comp += 15; + break; + case 1: + if (comp < 24) + comp = 24; + comp += 10; + break; + case 2: + case 3: + case 4: + if (comp < 16) + comp = 16; + } + if (comp <= 5) + tmp = 0; + else if (comp <= 10) + tmp = 10; + else if (comp <= 16) + tmp = 16; + else if (comp <= 24) + tmp = -1; + else + tmp = 0; + coding_method[ch][sb][j] = ((tmp & 0xfffa) + 30 )& 0xff; + } + for (sb = 0; sb < 30; sb++) + fix_coding_method_array(sb, nb_channels, coding_method); + for (ch = 0; ch < nb_channels; ch++) + for (sb = 0; sb < 30; sb++) + for (j = 0; j < 64; j++) + if (sb >= 10) { + if (coding_method[ch][sb][j] < 10) + coding_method[ch][sb][j] = 10; + } else { + if (sb >= 2) { + if (coding_method[ch][sb][j] < 16) + coding_method[ch][sb][j] = 16; + } else { + if (coding_method[ch][sb][j] < 30) + coding_method[ch][sb][j] = 30; + } + } + } else { // superblocktype_2_3 != 0 + for (ch = 0; ch < nb_channels; ch++) + for (sb = 0; sb < 30; sb++) + for (j = 0; j < 64; j++) + coding_method[ch][sb][j] = coding_method_table[cm_table_select][sb]; + } +} + +/** + * + * Called by process_subpacket_11 to process more data from subpacket 11 with sb 0-8 + * Called by process_subpacket_12 to process data from subpacket 12 with sb 8-sb_used + * + * @param gb bitreader context + * @param length packet length in bits + * @param sb_min lower subband processed (sb_min included) + * @param sb_max higher subband processed (sb_max excluded) + */ +void QDM2Stream::synthfilt_build_sb_samples(GetBitContext *gb, int length, int sb_min, int sb_max) { + int sb, j, k, n, ch, run, channels; + int joined_stereo, zero_encoding, chs; + int type34_first; + float type34_div = 0; + float type34_predictor; + float samples[10], sign_bits[16]; + + if (length == 0) { + // If no data use noise + for (sb = sb_min; sb < sb_max; sb++) + build_sb_samples_from_noise(sb); + + return; + } + + for (sb = sb_min; sb < sb_max; sb++) { + FIX_NOISE_IDX(_noiseIdx); + + channels = _channels; + + if (_channels <= 1 || sb < 12) + joined_stereo = 0; + else if (sb >= 24) + joined_stereo = 1; + else + joined_stereo = (BITS_LEFT(length,gb) >= 1) ? getBits1 (gb) : 0; + + if (joined_stereo) { + if (BITS_LEFT(length,gb) >= 16) + for (j = 0; j < 16; j++) + sign_bits[j] = getBits1(gb); + + for (j = 0; j < 64; j++) + if (_codingMethod[1][sb][j] > _codingMethod[0][sb][j]) + _codingMethod[0][sb][j] = _codingMethod[1][sb][j]; + + fix_coding_method_array(sb, _channels, _codingMethod); + channels = 1; + } + + for (ch = 0; ch < channels; ch++) { + zero_encoding = (BITS_LEFT(length,gb) >= 1) ? getBits1(gb) : 0; + type34_predictor = 0.0; + type34_first = 1; + + for (j = 0; j < 128; ) { + switch (_codingMethod[ch][sb][j / 2]) { + case 8: + if (BITS_LEFT(length,gb) >= 10) { + if (zero_encoding) { + for (k = 0; k < 5; k++) { + if ((j + 2 * k) >= 128) + break; + samples[2 * k] = getBits1(gb) ? dequant_1bit[joined_stereo][2 * getBits1(gb)] : 0; + } + } else { + n = getBits(gb, 8); + for (k = 0; k < 5; k++) + samples[2 * k] = dequant_1bit[joined_stereo][_randomDequantIndex[n][k]]; + } + for (k = 0; k < 5; k++) + samples[2 * k + 1] = SB_DITHERING_NOISE(sb, _noiseIdx); + } else { + for (k = 0; k < 10; k++) + samples[k] = SB_DITHERING_NOISE(sb, _noiseIdx); + } + run = 10; + break; + + case 10: + if (BITS_LEFT(length,gb) >= 1) { + double f = 0.81; + + if (getBits1(gb)) + f = -f; + f -= _noiseSamples[((sb + 1) * (j +5 * ch + 1)) & 127] * 9.0 / 40.0; + samples[0] = f; + } else { + samples[0] = SB_DITHERING_NOISE(sb, _noiseIdx); + } + run = 1; + break; + + case 16: + if (BITS_LEFT(length,gb) >= 10) { + if (zero_encoding) { + for (k = 0; k < 5; k++) { + if ((j + k) >= 128) + break; + samples[k] = (getBits1(gb) == 0) ? 0 : dequant_1bit[joined_stereo][2 * getBits1(gb)]; + } + } else { + n = getBits (gb, 8); + for (k = 0; k < 5; k++) + samples[k] = dequant_1bit[joined_stereo][_randomDequantIndex[n][k]]; + } + } else { + for (k = 0; k < 5; k++) + samples[k] = SB_DITHERING_NOISE(sb, _noiseIdx); + } + run = 5; + break; + + case 24: + if (BITS_LEFT(length,gb) >= 7) { + n = getBits(gb, 7); + for (k = 0; k < 3; k++) + samples[k] = (_randomDequantType24[n][k] - 2.0) * 0.5; + } else { + for (k = 0; k < 3; k++) + samples[k] = SB_DITHERING_NOISE(sb, _noiseIdx); + } + run = 3; + break; + + case 30: + if (BITS_LEFT(length,gb) >= 4) + samples[0] = type30_dequant[qdm2_get_vlc(gb, &_vlcTabType30, 0, 1)]; + else + samples[0] = SB_DITHERING_NOISE(sb, _noiseIdx); + + run = 1; + break; + + case 34: + if (BITS_LEFT(length,gb) >= 7) { + if (type34_first) { + type34_div = (float)(1 << getBits(gb, 2)); + samples[0] = ((float)getBits(gb, 5) - 16.0) / 15.0; + type34_predictor = samples[0]; + type34_first = 0; + } else { + samples[0] = type34_delta[qdm2_get_vlc(gb, &_vlcTabType34, 0, 1)] / type34_div + type34_predictor; + type34_predictor = samples[0]; + } + } else { + samples[0] = SB_DITHERING_NOISE(sb, _noiseIdx); + } + run = 1; + break; + + default: + samples[0] = SB_DITHERING_NOISE(sb, _noiseIdx); + run = 1; + break; + } + + if (joined_stereo) { + float tmp[10][MPA_MAX_CHANNELS]; + + for (k = 0; k < run; k++) { + tmp[k][0] = samples[k]; + tmp[k][1] = (sign_bits[(j + k) / 8]) ? -samples[k] : samples[k]; + } + for (chs = 0; chs < _channels; chs++) + for (k = 0; k < run; k++) + if ((j + k) < 128) + _sbSamples[chs][j + k][sb] = (int32)(_toneLevel[chs][sb][((j + k)/2)] * tmp[k][chs] + .5); + } else { + for (k = 0; k < run; k++) + if ((j + k) < 128) + _sbSamples[ch][j + k][sb] = (int32)(_toneLevel[ch][sb][(j + k)/2] * samples[k] + .5); + } + + j += run; + } // j loop + } // channel loop + } // subband loop +} + +/** + * Init the first element of a channel in quantized_coeffs with data from packet 10 (quantized_coeffs[ch][0]). + * This is similar to process_subpacket_9, but for a single channel and for element [0] + * same VLC tables as process_subpacket_9 are used. + * + * @param quantized_coeffs pointer to quantized_coeffs[ch][0] + * @param gb bitreader context + * @param length packet length in bits + */ +void QDM2Stream::init_quantized_coeffs_elem0(int8 *quantized_coeffs, GetBitContext *gb, int length) { + int i, k, run, level, diff; + + if (BITS_LEFT(length,gb) < 16) + return; + level = qdm2_get_vlc(gb, &_vlcTabLevel, 0, 2); + + quantized_coeffs[0] = level; + + for (i = 0; i < 7; ) { + if (BITS_LEFT(length,gb) < 16) + break; + run = qdm2_get_vlc(gb, &_vlcTabRun, 0, 1) + 1; + + if (BITS_LEFT(length,gb) < 16) + break; + diff = qdm2_get_se_vlc(&_vlcTabDiff, gb, 2); + + for (k = 1; k <= run; k++) + quantized_coeffs[i + k] = (level + ((k * diff) / run)); + + level += diff; + i += run; + } +} + +/** + * Related to synthesis filter, process data from packet 10 + * Init part of quantized_coeffs via function init_quantized_coeffs_elem0 + * Init tone_level_idx_hi1, tone_level_idx_hi2, tone_level_idx_mid with data from packet 10 + * + * @param gb bitreader context + * @param length packet length in bits + */ +void QDM2Stream::init_tone_level_dequantization(GetBitContext *gb, int length) { + int sb, j, k, n, ch; + + for (ch = 0; ch < _channels; ch++) { + init_quantized_coeffs_elem0(_quantizedCoeffs[ch][0], gb, length); + + if (BITS_LEFT(length,gb) < 16) { + memset(_quantizedCoeffs[ch][0], 0, 8); + break; + } + } + + n = _subSampling + 1; + + for (sb = 0; sb < n; sb++) + for (ch = 0; ch < _channels; ch++) + for (j = 0; j < 8; j++) { + if (BITS_LEFT(length,gb) < 1) + break; + if (getBits1(gb)) { + for (k=0; k < 8; k++) { + if (BITS_LEFT(length,gb) < 16) + break; + _toneLevelIdxHi1[ch][sb][j][k] = qdm2_get_vlc(gb, &_vlcTabToneLevelIdxHi1, 0, 2); + } + } else { + for (k=0; k < 8; k++) + _toneLevelIdxHi1[ch][sb][j][k] = 0; + } + } + + n = QDM2_SB_USED(_subSampling) - 4; + + for (sb = 0; sb < n; sb++) + for (ch = 0; ch < _channels; ch++) { + if (BITS_LEFT(length,gb) < 16) + break; + _toneLevelIdxHi2[ch][sb] = qdm2_get_vlc(gb, &_vlcTabToneLevelIdxHi2, 0, 2); + if (sb > 19) + _toneLevelIdxHi2[ch][sb] -= 16; + else + for (j = 0; j < 8; j++) + _toneLevelIdxMid[ch][sb][j] = -16; + } + + n = QDM2_SB_USED(_subSampling) - 5; + + for (sb = 0; sb < n; sb++) { + for (ch = 0; ch < _channels; ch++) { + for (j = 0; j < 8; j++) { + if (BITS_LEFT(length,gb) < 16) + break; + _toneLevelIdxMid[ch][sb][j] = qdm2_get_vlc(gb, &_vlcTabToneLevelIdxMid, 0, 2) - 32; + } + } + } +} + +/** + * Process subpacket 9, init quantized_coeffs with data from it + * + * @param node pointer to node with packet + */ +void QDM2Stream::process_subpacket_9(QDM2SubPNode *node) { + GetBitContext gb; + int i, j, k, n, ch, run, level, diff; + + initGetBits(&gb, node->packet->data, node->packet->size*8); + + n = coeff_per_sb_for_avg[_coeffPerSbSelect][QDM2_SB_USED(_subSampling) - 1] + 1; // same as averagesomething function + + for (i = 1; i < n; i++) + for (ch = 0; ch < _channels; ch++) { + level = qdm2_get_vlc(&gb, &_vlcTabLevel, 0, 2); + _quantizedCoeffs[ch][i][0] = level; + + for (j = 0; j < (8 - 1); ) { + run = qdm2_get_vlc(&gb, &_vlcTabRun, 0, 1) + 1; + diff = qdm2_get_se_vlc(&_vlcTabDiff, &gb, 2); + + for (k = 1; k <= run; k++) + _quantizedCoeffs[ch][i][j + k] = (level + ((k*diff) / run)); + + level += diff; + j += run; + } + } + + for (ch = 0; ch < _channels; ch++) + for (i = 0; i < 8; i++) + _quantizedCoeffs[ch][0][i] = 0; +} + +/** + * Process subpacket 10 if not null, else + * + * @param node pointer to node with packet + * @param length packet length in bits + */ +void QDM2Stream::process_subpacket_10(QDM2SubPNode *node, int length) { + GetBitContext gb; + + initGetBits(&gb, ((node == NULL) ? _emptyBuffer : node->packet->data), ((node == NULL) ? 0 : node->packet->size*8)); + + if (length != 0) { + init_tone_level_dequantization(&gb, length); + fill_tone_level_array(1); + } else { + fill_tone_level_array(0); + } +} + +/** + * Process subpacket 11 + * + * @param node pointer to node with packet + * @param length packet length in bit + */ +void QDM2Stream::process_subpacket_11(QDM2SubPNode *node, int length) { + GetBitContext gb; + + initGetBits(&gb, ((node == NULL) ? _emptyBuffer : node->packet->data), ((node == NULL) ? 0 : node->packet->size*8)); + if (length >= 32) { + int c = getBits (&gb, 13); + + if (c > 3) + fill_coding_method_array(_toneLevelIdx, _toneLevelIdxTemp, _codingMethod, + _channels, 8*c, _superblocktype_2_3, _cmTableSelect); + } + + synthfilt_build_sb_samples(&gb, length, 0, 8); +} + +/** + * Process subpacket 12 + * + * @param node pointer to node with packet + * @param length packet length in bits + */ +void QDM2Stream::process_subpacket_12(QDM2SubPNode *node, int length) { + GetBitContext gb; + + initGetBits(&gb, ((node == NULL) ? _emptyBuffer : node->packet->data), ((node == NULL) ? 0 : node->packet->size*8)); + synthfilt_build_sb_samples(&gb, length, 8, QDM2_SB_USED(_subSampling)); +} + +/* + * Process new subpackets for synthesis filter + * + * @param list list with synthesis filter packets (list D) + */ +void QDM2Stream::process_synthesis_subpackets(QDM2SubPNode *list) { + struct QDM2SubPNode *nodes[4]; + + nodes[0] = qdm2_search_subpacket_type_in_list(list, 9); + if (nodes[0] != NULL) + process_subpacket_9(nodes[0]); + + nodes[1] = qdm2_search_subpacket_type_in_list(list, 10); + if (nodes[1] != NULL) + process_subpacket_10(nodes[1], nodes[1]->packet->size << 3); + else + process_subpacket_10(NULL, 0); + + nodes[2] = qdm2_search_subpacket_type_in_list(list, 11); + if (nodes[0] != NULL && nodes[1] != NULL && nodes[2] != NULL) + process_subpacket_11(nodes[2], (nodes[2]->packet->size << 3)); + else + process_subpacket_11(NULL, 0); + + nodes[3] = qdm2_search_subpacket_type_in_list(list, 12); + if (nodes[0] != NULL && nodes[1] != NULL && nodes[3] != NULL) + process_subpacket_12(nodes[3], (nodes[3]->packet->size << 3)); + else + process_subpacket_12(NULL, 0); +} + +/* + * Decode superblock, fill packet lists. + * + */ +void QDM2Stream::qdm2_decode_super_block(void) { + GetBitContext gb; + struct QDM2SubPacket header, *packet; + int i, packet_bytes, sub_packet_size, subPacketsD; + unsigned int next_index = 0; + + memset(_toneLevelIdxHi1, 0, sizeof(_toneLevelIdxHi1)); + memset(_toneLevelIdxMid, 0, sizeof(_toneLevelIdxMid)); + memset(_toneLevelIdxHi2, 0, sizeof(_toneLevelIdxHi2)); + + _subPacketsB = 0; + subPacketsD = 0; + + average_quantized_coeffs(); // average elements in quantized_coeffs[max_ch][10][8] + + initGetBits(&gb, _compressedData, _packetSize*8); + qdm2_decode_sub_packet_header(&gb, &header); + + if (header.type < 2 || header.type >= 8) { + _hasErrors = true; + error("QDM2 : bad superblock type"); + return; + } + + _superblocktype_2_3 = (header.type == 2 || header.type == 3); + packet_bytes = (_packetSize - getBitsCount(&gb) / 8); + + initGetBits(&gb, header.data, header.size*8); + + if (header.type == 2 || header.type == 4 || header.type == 5) { + int csum = 257 * getBits(&gb, 8) + 2 * getBits(&gb, 8); + + csum = qdm2_packet_checksum(_compressedData, _packetSize, csum); + + if (csum != 0) { + _hasErrors = true; + error("QDM2 : bad packet checksum"); + return; + } + } + + _subPacketListB[0].packet = NULL; + _subPacketListD[0].packet = NULL; + + for (i = 0; i < 6; i++) + if (--_fftLevelExp[i] < 0) + _fftLevelExp[i] = 0; + + for (i = 0; packet_bytes > 0; i++) { + int j; + + _subPacketListA[i].next = NULL; + + if (i > 0) { + _subPacketListA[i - 1].next = &_subPacketListA[i]; + + // seek to next block + initGetBits(&gb, header.data, header.size*8); + skipBits(&gb, next_index*8); + + if (next_index >= header.size) + break; + } + + // decode subpacket + packet = &_subPackets[i]; + qdm2_decode_sub_packet_header(&gb, packet); + next_index = packet->size + getBitsCount(&gb) / 8; + sub_packet_size = ((packet->size > 0xff) ? 1 : 0) + packet->size + 2; + + if (packet->type == 0) + break; + + if (sub_packet_size > packet_bytes) { + if (packet->type != 10 && packet->type != 11 && packet->type != 12) + break; + packet->size += packet_bytes - sub_packet_size; + } + + packet_bytes -= sub_packet_size; + + // add subpacket to 'all subpackets' list + _subPacketListA[i].packet = packet; + + // add subpacket to related list + if (packet->type == 8) { + error("Unsupported packet type 8"); + return; + } else if (packet->type >= 9 && packet->type <= 12) { + // packets for MPEG Audio like Synthesis Filter + QDM2_LIST_ADD(_subPacketListD, subPacketsD, packet); + } else if (packet->type == 13) { + for (j = 0; j < 6; j++) + _fftLevelExp[j] = getBits(&gb, 6); + } else if (packet->type == 14) { + for (j = 0; j < 6; j++) + _fftLevelExp[j] = qdm2_get_vlc(&gb, &_fftLevelExpVlc, 0, 2); + } else if (packet->type == 15) { + error("Unsupported packet type 15"); + return; + } else if (packet->type >= 16 && packet->type < 48 && !fft_subpackets[packet->type - 16]) { + // packets for FFT + QDM2_LIST_ADD(_subPacketListB, _subPacketsB, packet); + } + } // Packet bytes loop + +// **************************************************************** + if (_subPacketListD[0].packet != NULL) { + process_synthesis_subpackets(_subPacketListD); + _doSynthFilter = 1; + } else if (_doSynthFilter) { + process_subpacket_10(NULL, 0); + process_subpacket_11(NULL, 0); + process_subpacket_12(NULL, 0); + } +// **************************************************************** +} + +void QDM2Stream::qdm2_fft_init_coefficient(int sub_packet, int offset, int duration, + int channel, int exp, int phase) { + if (_fftCoefsMinIndex[duration] < 0) + _fftCoefsMinIndex[duration] = _fftCoefsIndex; + + _fftCoefs[_fftCoefsIndex].sub_packet = ((sub_packet >= 16) ? (sub_packet - 16) : sub_packet); + _fftCoefs[_fftCoefsIndex].channel = channel; + _fftCoefs[_fftCoefsIndex].offset = offset; + _fftCoefs[_fftCoefsIndex].exp = exp; + _fftCoefs[_fftCoefsIndex].phase = phase; + _fftCoefsIndex++; +} + +void QDM2Stream::qdm2_fft_decode_tones(int duration, GetBitContext *gb, int b) { + debug(1, "QDM2Stream::qdm2_fft_decode_tones() duration: %d b:%d", duration, b); + int channel, stereo, phase, exp; + int local_int_4, local_int_8, stereo_phase, local_int_10; + int local_int_14, stereo_exp, local_int_20, local_int_28; + int n, offset; + + local_int_4 = 0; + local_int_28 = 0; + local_int_20 = 2; + local_int_8 = (4 - duration); + local_int_10 = 1 << (_groupOrder - duration - 1); + offset = 1; + + while (1) { + if (_superblocktype_2_3) { + debug(1, "QDM2Stream::qdm2_fft_decode_tones() local_int_8: %d", local_int_8); + while ((n = qdm2_get_vlc(gb, &_vlcTabFftToneOffset[local_int_8], 1, 2)) < 2) { + debug(1, "QDM2Stream::qdm2_fft_decode_tones() local_int_8: %d", local_int_8); + offset = 1; + if (n == 0) { + local_int_4 += local_int_10; + local_int_28 += (1 << local_int_8); + } else { + local_int_4 += 8*local_int_10; + local_int_28 += (8 << local_int_8); + } + } + offset += (n - 2); + } else { + offset += qdm2_get_vlc(gb, &_vlcTabFftToneOffset[local_int_8], 1, 2); + while (offset >= (local_int_10 - 1)) { + offset += (1 - (local_int_10 - 1)); + local_int_4 += local_int_10; + local_int_28 += (1 << local_int_8); + } + } + + if (local_int_4 >= _blockSize) + return; + + local_int_14 = (offset >> local_int_8); + + if (_channels > 1) { + channel = getBits1(gb); + stereo = getBits1(gb); + } else { + channel = 0; + stereo = 0; + } + + exp = qdm2_get_vlc(gb, (b ? &_fftLevelExpVlc : &_fftLevelExpAltVlc), 0, 2); + exp += _fftLevelExp[fft_level_index_table[local_int_14]]; + exp = (exp < 0) ? 0 : exp; + + phase = getBits(gb, 3); + stereo_exp = 0; + stereo_phase = 0; + + if (stereo) { + stereo_exp = (exp - qdm2_get_vlc(gb, &_fftStereoExpVlc, 0, 1)); + stereo_phase = (phase - qdm2_get_vlc(gb, &_fftStereoPhaseVlc, 0, 1)); + if (stereo_phase < 0) + stereo_phase += 8; + } + + if (_frequencyRange > (local_int_14 + 1)) { + int sub_packet = (local_int_20 + local_int_28); + + qdm2_fft_init_coefficient(sub_packet, offset, duration, channel, exp, phase); + if (stereo) + qdm2_fft_init_coefficient(sub_packet, offset, duration, (1 - channel), stereo_exp, stereo_phase); + } + + offset++; + } +} + +void QDM2Stream::qdm2_decode_fft_packets(void) { + debug(1, "QDM2Stream::qdm2_decode_fft_packets()"); + int i, j, min, max, value, type, unknown_flag; + GetBitContext gb; + + if (_subPacketListB[0].packet == NULL) + return; + + // reset minimum indexes for FFT coefficients + _fftCoefsIndex = 0; + for (i=0; i < 5; i++) + _fftCoefsMinIndex[i] = -1; + + // process subpackets ordered by type, largest type first + for (i = 0, max = 256; i < _subPacketsB; i++) { + QDM2SubPacket *packet= NULL; + + // find subpacket with largest type less than max + for (j = 0, min = 0; j < _subPacketsB; j++) { + value = _subPacketListB[j].packet->type; + if (value > min && value < max) { + min = value; + packet = _subPacketListB[j].packet; + } + } + + max = min; + + // check for errors (?) + if (!packet) + return; + + if (i == 0 && (packet->type < 16 || packet->type >= 48 || fft_subpackets[packet->type - 16])) + return; + + // decode FFT tones + debug(1, "QDM2Stream::qdm2_decode_fft_packets initGetBits() packet->size*8: %d", packet->size*8); + initGetBits(&gb, packet->data, packet->size*8); + + if (packet->type >= 32 && packet->type < 48 && !fft_subpackets[packet->type - 16]) + unknown_flag = 1; + else + unknown_flag = 0; + + type = packet->type; + + if ((type >= 17 && type < 24) || (type >= 33 && type < 40)) { + int duration = _subSampling + 5 - (type & 15); + + if (duration >= 0 && duration < 4) { // TODO: Should be <= 4? + debug(1, "QDM2Stream::qdm2_decode_fft_packets qdm2_fft_decode_tones() #1"); + qdm2_fft_decode_tones(duration, &gb, unknown_flag); + } + } else if (type == 31) { + for (j=0; j < 4; j++) { + debug(1, "QDM2Stream::qdm2_decode_fft_packets qdm2_fft_decode_tones() #2"); + qdm2_fft_decode_tones(j, &gb, unknown_flag); + } + } else if (type == 46) { + for (j=0; j < 6; j++) + _fftLevelExp[j] = getBits(&gb, 6); + for (j=0; j < 4; j++) { + debug(1, "QDM2Stream::qdm2_decode_fft_packets qdm2_fft_decode_tones() #3"); + qdm2_fft_decode_tones(j, &gb, unknown_flag); + } + } + } // Loop on B packets + + // calculate maximum indexes for FFT coefficients + for (i = 0, j = -1; i < 5; i++) + if (_fftCoefsMinIndex[i] >= 0) { + if (j >= 0) + _fftCoefsMaxIndex[j] = _fftCoefsMinIndex[i]; + j = i; + } + if (j >= 0) + _fftCoefsMaxIndex[j] = _fftCoefsIndex; +} + +void QDM2Stream::qdm2_fft_generate_tone(FFTTone *tone) +{ + float level, f[6]; + int i; + QDM2Complex c; + const double iscale = 2.0 * PI / 512.0; + + tone->phase += tone->phase_shift; + + // calculate current level (maximum amplitude) of tone + level = fft_tone_envelope_table[tone->duration][tone->time_index] * tone->level; + c.im = level * sin(tone->phase*iscale); + c.re = level * cos(tone->phase*iscale); + + // generate FFT coefficients for tone + if (tone->duration >= 3 || tone->cutoff >= 3) { + tone->complex[0].im += c.im; + tone->complex[0].re += c.re; + tone->complex[1].im -= c.im; + tone->complex[1].re -= c.re; + } else { + f[1] = -tone->table[4]; + f[0] = tone->table[3] - tone->table[0]; + f[2] = 1.0 - tone->table[2] - tone->table[3]; + f[3] = tone->table[1] + tone->table[4] - 1.0; + f[4] = tone->table[0] - tone->table[1]; + f[5] = tone->table[2]; + for (i = 0; i < 2; i++) { + tone->complex[fft_cutoff_index_table[tone->cutoff][i]].re += c.re * f[i]; + tone->complex[fft_cutoff_index_table[tone->cutoff][i]].im += c.im *((tone->cutoff <= i) ? -f[i] : f[i]); + } + for (i = 0; i < 4; i++) { + tone->complex[i].re += c.re * f[i+2]; + tone->complex[i].im += c.im * f[i+2]; + } + } + + // copy the tone if it has not yet died out + if (++tone->time_index < ((1 << (5 - tone->duration)) - 1)) { + memcpy(&_fftTones[_fftToneEnd], tone, sizeof(FFTTone)); + _fftToneEnd = (_fftToneEnd + 1) % 1000; + } +} + +void QDM2Stream::qdm2_fft_tone_synthesizer(uint8 sub_packet) { + int i, j, ch; + const double iscale = 0.25 * PI; + + for (ch = 0; ch < _channels; ch++) { + memset(_fft.complex[ch], 0, _frameSize * sizeof(QDM2Complex)); + } + + // apply FFT tones with duration 4 (1 FFT period) + if (_fftCoefsMinIndex[4] >= 0) + for (i = _fftCoefsMinIndex[4]; i < _fftCoefsMaxIndex[4]; i++) { + float level; + QDM2Complex c; + + if (_fftCoefs[i].sub_packet != sub_packet) + break; + + ch = (_channels == 1) ? 0 : _fftCoefs[i].channel; + level = (_fftCoefs[i].exp < 0) ? 0.0 : fft_tone_level_table[_superblocktype_2_3 ? 0 : 1][_fftCoefs[i].exp & 63]; + + c.re = level * cos(_fftCoefs[i].phase * iscale); + c.im = level * sin(_fftCoefs[i].phase * iscale); + _fft.complex[ch][_fftCoefs[i].offset + 0].re += c.re; + _fft.complex[ch][_fftCoefs[i].offset + 0].im += c.im; + _fft.complex[ch][_fftCoefs[i].offset + 1].re -= c.re; + _fft.complex[ch][_fftCoefs[i].offset + 1].im -= c.im; + } + + // generate existing FFT tones + for (i = _fftToneEnd; i != _fftToneStart; ) { + qdm2_fft_generate_tone(&_fftTones[_fftToneStart]); + _fftToneStart = (_fftToneStart + 1) % 1000; + } + + // create and generate new FFT tones with duration 0 (long) to 3 (short) + for (i = 0; i < 4; i++) + if (_fftCoefsMinIndex[i] >= 0) { + for (j = _fftCoefsMinIndex[i]; j < _fftCoefsMaxIndex[i]; j++) { + int offset, four_i; + FFTTone tone; + + if (_fftCoefs[j].sub_packet != sub_packet) + break; + + four_i = (4 - i); + offset = _fftCoefs[j].offset >> four_i; + ch = (_channels == 1) ? 0 : _fftCoefs[j].channel; + + if (offset < _frequencyRange) { + if (offset < 2) + tone.cutoff = offset; + else + tone.cutoff = (offset >= 60) ? 3 : 2; + + tone.level = (_fftCoefs[j].exp < 0) ? 0.0 : fft_tone_level_table[_superblocktype_2_3 ? 0 : 1][_fftCoefs[j].exp & 63]; + tone.complex = &_fft.complex[ch][offset]; + tone.table = fft_tone_sample_table[i][_fftCoefs[j].offset - (offset << four_i)]; + tone.phase = 64 * _fftCoefs[j].phase - (offset << 8) - 128; + tone.phase_shift = (2 * _fftCoefs[j].offset + 1) << (7 - four_i); + tone.duration = i; + tone.time_index = 0; + + qdm2_fft_generate_tone(&tone); + } + } + _fftCoefsMinIndex[i] = j; + } +} + +void QDM2Stream::qdm2_calculate_fft(int channel) { + debug(1, "QDM2Stream::qdm2_calculate_fft channel: %d", channel); + const float gain = (_channels == 1 && _channels == 2) ? 0.5f : 1.0f; + int i; + + _fft.complex[channel][0].re *= 2.0f; + _fft.complex[channel][0].im = 0.0f; + + //debug(1, "QDM2Stream::qdm2_calculate_fft _fft.complex[channel][0].re: %lf", _fft.complex[channel][0].re); + //debug(1, "QDM2Stream::qdm2_calculate_fft _fft.complex[channel][0].im: %lf", _fft.complex[channel][0].im); + + rdftCalc(&_rdftCtx, (float *)_fft.complex[channel]); + + // add samples to output buffer + for (i = 0; i < ((_fftFrameSize + 15) & ~15); i++) + _outputBuffer[_channels * i + channel] += ((float *) _fft.complex[channel])[i] * gain; +} + +/** + * @param index subpacket number + */ +void QDM2Stream::qdm2_synthesis_filter(uint8 index) +{ + int16 samples[MPA_MAX_CHANNELS * MPA_FRAME_SIZE]; + int i, k, ch, sb_used, sub_sampling, dither_state = 0; + + // copy sb_samples + sb_used = QDM2_SB_USED(_subSampling); + + for (ch = 0; ch < _channels; ch++) + for (i = 0; i < 8; i++) + for (k = sb_used; k < 32; k++) + _sbSamples[ch][(8 * index) + i][k] = 0; + + for (ch = 0; ch < _channels; ch++) { + int16 *samples_ptr = samples + ch; + + for (i = 0; i < 8; i++) { + ff_mpa_synth_filter(_synthBuf[ch], &(_synthBufOffset[ch]), + ff_mpa_synth_window, &dither_state, + samples_ptr, _channels, + _sbSamples[ch][(8 * index) + i]); + samples_ptr += 32 * _channels; + } + } + + // add samples to output buffer + sub_sampling = (4 >> _subSampling); + + for (ch = 0; ch < _channels; ch++) + for (i = 0; i < _sFrameSize; i++) + _outputBuffer[_channels * i + ch] += (float)(samples[_channels * sub_sampling * i + ch] >> (sizeof(int16)*8-16)); +} + +int QDM2Stream::qdm2_decodeFrame(Common::SeekableReadStream *in) { + debug(1, "QDM2Stream::qdm2_decodeFrame in->pos(): %d in->size(): %d", in->pos(), in->size()); + int ch, i; + const int frame_size = (_sFrameSize * _channels); + + // select input buffer + if(in->eos() || in->size() == in->pos()) { + debug(1, "QDM2Stream::qdm2_decodeFrame End of Input Stream"); + return 0; + } + if((in->size() - in->pos()) < _packetSize) { + debug(1, "QDM2Stream::qdm2_decodeFrame Insufficient Packet Data in Input Stream Found: %d Need: %d", in->size() - in->pos(), _packetSize); + return 0; + } + + in->read(_compressedData, _packetSize); + debug(1, "QDM2Stream::qdm2_decodeFrame constructed input data"); + + // copy old block, clear new block of output samples + memmove(_outputBuffer, &_outputBuffer[frame_size], frame_size * sizeof(float)); + memset(&_outputBuffer[frame_size], 0, frame_size * sizeof(float)); + debug(1, "QDM2Stream::qdm2_decodeFrame cleared outputBuffer"); + + // decode block of QDM2 compressed data + debug(1, "QDM2Stream::qdm2_decodeFrame decode block of QDM2 compressed data"); + if (_subPacket == 0) { + _hasErrors = false; // reset it for a new super block + debug(1, "QDM2 : Superblock follows"); + qdm2_decode_super_block(); + } + + // parse subpackets + debug(1, "QDM2Stream::qdm2_decodeFrame parse subpackets"); + if (!_hasErrors) { + if (_subPacket == 2) { + debug(1, "QDM2Stream::qdm2_decodeFrame qdm2_decode_fft_packets()"); + qdm2_decode_fft_packets(); + } + + debug(1, "QDM2Stream::qdm2_decodeFrame qdm2_fft_tone_synthesizer(%d)", _subPacket); + qdm2_fft_tone_synthesizer(_subPacket); + } + + // sound synthesis stage 1 (FFT) + debug(1, "QDM2Stream::qdm2_decodeFrame sound synthesis stage 1 (FFT)"); + for (ch = 0; ch < _channels; ch++) { + qdm2_calculate_fft(ch); + + if (!_hasErrors && _subPacketListC[0].packet != NULL) { + error("QDM2 : has errors, and C list is not empty"); + return 0; + } + } + + // sound synthesis stage 2 (MPEG audio like synthesis filter) + debug(1, "QDM2Stream::qdm2_decodeFrame sound synthesis stage 2 (MPEG audio like synthesis filter)"); + if (!_hasErrors && _doSynthFilter) + qdm2_synthesis_filter(_subPacket); + + _subPacket = (_subPacket + 1) % 16; + + if(_hasErrors) + warning("QDM2 Packet error..."); + + // clip and convert output float[] to 16bit signed samples + debug(1, "QDM2Stream::qdm2_decodeFrame clip and convert output float[] to 16bit signed samples"); + +/* + debugN(1, "Input Data Packet:"); + for(i = 0; i < _packetSize; i++) { + debugN(1, " %d", _compressedData[i]); + } + debugN(1, " Output Data Packet:"); + for(i = 0; i < frame_size; i++) { + debugN(1, " %d", (int)_outputBuffer[i]); + } + debug(1, ""); +*/ + + for (i = 0; i < frame_size; i++) { + //debug(1, "QDM2Stream::qdm2_decodeFrame i: %d", i); + int value = (int)_outputBuffer[i]; + + if (value > SOFTCLIP_THRESHOLD) + value = (value > HARDCLIP_THRESHOLD) ? 32767 : _softclipTable[ value - SOFTCLIP_THRESHOLD]; + else if (value < -SOFTCLIP_THRESHOLD) + value = (value < -HARDCLIP_THRESHOLD) ? -32767 : -_softclipTable[-value - SOFTCLIP_THRESHOLD]; + + _outputSamples.push_back(value); + } + return frame_size; +} + +int QDM2Stream::readBuffer(int16 *buffer, const int numSamples) { + debug(1, "QDM2Stream::readBuffer numSamples: %d", numSamples); + int32 decodedSamples = _outputSamples.size(); + int32 i; + + //while((int)_outputSamples.size() < numSamples) { + while(!_stream->eos() && _stream->pos() != _stream->size()) { + i = qdm2_decodeFrame(_stream); + if(i == 0) + break; // Out Of Decode Frames... + decodedSamples += i; + } + if(decodedSamples > numSamples) + decodedSamples = numSamples; + + for(i = 0; i < decodedSamples; i++) + buffer[i] = _outputSamples.remove_at(0); + + return decodedSamples; +} + +} // End of namespace Mohawk diff --git a/engines/mohawk/video/qdm2.h b/engines/mohawk/video/qdm2.h new file mode 100644 index 0000000000..ffa5f77030 --- /dev/null +++ b/engines/mohawk/video/qdm2.h @@ -0,0 +1,289 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef MOHAWK_VIDEO_QDM2_H +#define MOHAWK_VIDEO_QDM2_H + +#include "sound/audiostream.h" +#include "common/array.h" +#include "common/stream.h" + +namespace Mohawk { + +enum { + SOFTCLIP_THRESHOLD = 27600, + HARDCLIP_THRESHOLD = 35716, + MPA_MAX_CHANNELS = 2, + MPA_FRAME_SIZE = 1152, + FF_INPUT_BUFFER_PADDING_SIZE = 8 +}; + +typedef int8 sb_int8_array[2][30][64]; + +/* bit input */ +/* buffer, buffer_end and size_in_bits must be present and used by every reader */ +struct GetBitContext { + const uint8 *buffer, *bufferEnd; + int index; + int sizeInBits; +}; + +struct QDM2SubPacket { + int type; + unsigned int size; + const uint8 *data; // pointer to subpacket data (points to input data buffer, it's not a private copy) +}; + +struct QDM2SubPNode { + QDM2SubPacket *packet; + struct QDM2SubPNode *next; // pointer to next packet in the list, NULL if leaf node +}; + +struct QDM2Complex { + float re; + float im; +}; + +struct FFTTone { + float level; + QDM2Complex *complex; + const float *table; + int phase; + int phase_shift; + int duration; + short time_index; + short cutoff; +}; + +struct FFTCoefficient { + int16 sub_packet; + uint8 channel; + int16 offset; + int16 exp; + uint8 phase; +}; + +struct VLC { + int32 bits; + int16 (*table)[2]; // code, bits + int32 table_size; + int32 table_allocated; +}; + +#include "common/pack-start.h" +struct QDM2FFT { + QDM2Complex complex[MPA_MAX_CHANNELS][256]; +} PACKED_STRUCT; +#include "common/pack-end.h" + +enum RDFTransformType { + RDFT, + IRDFT, + RIDFT, + IRIDFT +}; + +struct FFTComplex { + float re, im; +}; + +struct FFTContext { + int nbits; + int inverse; + uint16 *revtab; + FFTComplex *exptab; + FFTComplex *tmpBuf; + int mdctSize; // size of MDCT (i.e. number of input data * 2) + int mdctBits; // n = 2^nbits + // pre/post rotation tables + float *tcos; + float *tsin; + void (*fftPermute)(struct FFTContext *s, FFTComplex *z); + void (*fftCalc)(struct FFTContext *s, FFTComplex *z); + void (*imdctCalc)(struct FFTContext *s, float *output, const float *input); + void (*imdctHalf)(struct FFTContext *s, float *output, const float *input); + void (*mdctCalc)(struct FFTContext *s, float *output, const float *input); + int splitRadix; + int permutation; +}; + +enum { + FF_MDCT_PERM_NONE = 0, + FF_MDCT_PERM_INTERLEAVE = 1 +}; + +struct RDFTContext { + int nbits; + int inverse; + int signConvention; + + // pre/post rotation tables + float *tcos; + float *tsin; + FFTContext fft; +}; + +class QDM2Stream : public Audio::AudioStream { +public: + QDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadStream *extraData); + ~QDM2Stream(); + + bool isStereo() const { return _channels == 2; } + bool endOfData() const { return ((_stream->pos() == _stream->size()) && (_outputSamples.size() == 0)); } + int getRate() const { return _sampleRate; } + int readBuffer(int16 *buffer, const int numSamples); + +private: + Common::SeekableReadStream *_stream; + + // Parameters from codec header, do not change during playback + uint8 _channels; + uint16 _sampleRate; + uint16 _bitRate; + uint16 _blockSize; // Group + uint16 _frameSize; // FFT + uint16 _packetSize; // Checksum + + // Parameters built from header parameters, do not change during playback + int _groupOrder; // order of frame group + int _fftOrder; // order of FFT (actually fft order+1) + int _fftFrameSize; // size of fft frame, in components (1 comples = re + im) + int _sFrameSize; // size of data frame + int _frequencyRange; + int _subSampling; // subsampling: 0=25%, 1=50%, 2=100% */ + int _coeffPerSbSelect; // selector for "num. of coeffs. per subband" tables. Can be 0, 1, 2 + int _cmTableSelect; // selector for "coding method" tables. Can be 0, 1 (from init: 0-4) + + // Packets and packet lists + QDM2SubPacket _subPackets[16]; // the packets themselves + QDM2SubPNode _subPacketListA[16]; // list of all packets + QDM2SubPNode _subPacketListB[16]; // FFT packets B are on list + int _subPacketsB; // number of packets on 'B' list + QDM2SubPNode _subPacketListC[16]; // packets with errors? + QDM2SubPNode _subPacketListD[16]; // DCT packets + + // FFT and tones + FFTTone _fftTones[1000]; + int _fftToneStart; + int _fftToneEnd; + FFTCoefficient _fftCoefs[1000]; + int _fftCoefsIndex; + int _fftCoefsMinIndex[5]; + int _fftCoefsMaxIndex[5]; + int _fftLevelExp[6]; + //RDFTContext _rdftCtx; + QDM2FFT _fft; + + // I/O data + uint8 *_compressedData; + float _outputBuffer[1024]; + Common::Array<int16> _outputSamples; + + // Synthesis filter + int16 ff_mpa_synth_window[512]; + int16 _synthBuf[MPA_MAX_CHANNELS][512*2]; + int _synthBufOffset[MPA_MAX_CHANNELS]; + int32 _sbSamples[MPA_MAX_CHANNELS][128][32]; + + // Mixed temporary data used in decoding + float _toneLevel[MPA_MAX_CHANNELS][30][64]; + int8 _codingMethod[MPA_MAX_CHANNELS][30][64]; + int8 _quantizedCoeffs[MPA_MAX_CHANNELS][10][8]; + int8 _toneLevelIdxBase[MPA_MAX_CHANNELS][30][8]; + int8 _toneLevelIdxHi1[MPA_MAX_CHANNELS][3][8][8]; + int8 _toneLevelIdxMid[MPA_MAX_CHANNELS][26][8]; + int8 _toneLevelIdxHi2[MPA_MAX_CHANNELS][26]; + int8 _toneLevelIdx[MPA_MAX_CHANNELS][30][64]; + int8 _toneLevelIdxTemp[MPA_MAX_CHANNELS][30][64]; + + // Flags + bool _hasErrors; // packet has errors + int _superblocktype_2_3; // select fft tables and some algorithm based on superblock type + int _doSynthFilter; // used to perform or skip synthesis filter + + uint8 _subPacket; // 0 to 15 + int _noiseIdx; // index for dithering noise table + + byte _emptyBuffer[FF_INPUT_BUFFER_PADDING_SIZE]; + + VLC _vlcTabLevel; + VLC _vlcTabDiff; + VLC _vlcTabRun; + VLC _fftLevelExpAltVlc; + VLC _fftLevelExpVlc; + VLC _fftStereoExpVlc; + VLC _fftStereoPhaseVlc; + VLC _vlcTabToneLevelIdxHi1; + VLC _vlcTabToneLevelIdxMid; + VLC _vlcTabToneLevelIdxHi2; + VLC _vlcTabType30; + VLC _vlcTabType34; + VLC _vlcTabFftToneOffset[5]; + bool _vlcsInitialized; + void initVlc(void); + + uint16 _softclipTable[HARDCLIP_THRESHOLD - SOFTCLIP_THRESHOLD + 1]; + void softclipTableInit(void); + + float _noiseTable[4096]; + byte _randomDequantIndex[256][5]; + byte _randomDequantType24[128][3]; + void rndTableInit(void); + + float _noiseSamples[128]; + void initNoiseSamples(void); + + RDFTContext _rdftCtx; + + void average_quantized_coeffs(void); + void build_sb_samples_from_noise(int sb); + void fix_coding_method_array(int sb, int channels, sb_int8_array coding_method); + void fill_tone_level_array(int flag); + void fill_coding_method_array(sb_int8_array tone_level_idx, sb_int8_array tone_level_idx_temp, + sb_int8_array coding_method, int nb_channels, + int c, int superblocktype_2_3, int cm_table_select); + void synthfilt_build_sb_samples(GetBitContext *gb, int length, int sb_min, int sb_max); + void init_quantized_coeffs_elem0(int8 *quantized_coeffs, GetBitContext *gb, int length); + void init_tone_level_dequantization(GetBitContext *gb, int length); + void process_subpacket_9(QDM2SubPNode *node); + void process_subpacket_10(QDM2SubPNode *node, int length); + void process_subpacket_11(QDM2SubPNode *node, int length); + void process_subpacket_12(QDM2SubPNode *node, int length); + void process_synthesis_subpackets(QDM2SubPNode *list); + void qdm2_decode_super_block(void); + void qdm2_fft_init_coefficient(int sub_packet, int offset, int duration, + int channel, int exp, int phase); + void qdm2_fft_decode_tones(int duration, GetBitContext *gb, int b); + void qdm2_decode_fft_packets(void); + void qdm2_fft_generate_tone(FFTTone *tone); + void qdm2_fft_tone_synthesizer(uint8 sub_packet); + void qdm2_calculate_fft(int channel); + void qdm2_synthesis_filter(uint8 index); + int qdm2_decodeFrame(Common::SeekableReadStream *in); +}; + +} // End of namespace Mohawk + +#endif diff --git a/engines/mohawk/video/qdm2data.h b/engines/mohawk/video/qdm2data.h new file mode 100644 index 0000000000..f1c18db41c --- /dev/null +++ b/engines/mohawk/video/qdm2data.h @@ -0,0 +1,531 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef MOHAWK_VIDEO_QDM2DATA_H +#define MOHAWK_VIDEO_QDM2DATA_H + +#include "common/scummsys.h" + +namespace Mohawk { + +/// VLC TABLES + +// values in this table range from -1..23; adjust retrieved value by -1 +static const uint16 vlc_tab_level_huffcodes[24] = { + 0x037c, 0x0004, 0x003c, 0x004c, 0x003a, 0x002c, 0x001c, 0x001a, + 0x0024, 0x0014, 0x0001, 0x0002, 0x0000, 0x0003, 0x0007, 0x0005, + 0x0006, 0x0008, 0x0009, 0x000a, 0x000c, 0x00fc, 0x007c, 0x017c +}; + +static const byte vlc_tab_level_huffbits[24] = { + 10, 6, 7, 7, 6, 6, 6, 6, 6, 5, 4, 4, 4, 3, 3, 3, 3, 4, 4, 5, 7, 8, 9, 10 +}; + +// values in this table range from -1..36; adjust retrieved value by -1 +static const uint16 vlc_tab_diff_huffcodes[37] = { + 0x1c57, 0x0004, 0x0000, 0x0001, 0x0003, 0x0002, 0x000f, 0x000e, + 0x0007, 0x0016, 0x0037, 0x0027, 0x0026, 0x0066, 0x0006, 0x0097, + 0x0046, 0x01c6, 0x0017, 0x0786, 0x0086, 0x0257, 0x00d7, 0x0357, + 0x00c6, 0x0386, 0x0186, 0x0000, 0x0157, 0x0c57, 0x0057, 0x0000, + 0x0b86, 0x0000, 0x1457, 0x0000, 0x0457 +}; + +static const byte vlc_tab_diff_huffbits[37] = { + 13, 3, 3, 2, 3, 3, 4, 4, 6, 5, 6, 6, 7, 7, 8, 8, + 8, 9, 8, 11, 9, 10, 8, 10, 9, 12, 10, 0, 10, 13, 11, 0, + 12, 0, 13, 0, 13 +}; + +// values in this table range from -1..5; adjust retrieved value by -1 +static const byte vlc_tab_run_huffcodes[6] = { + 0x1f, 0x00, 0x01, 0x03, 0x07, 0x0f +}; + +static const byte vlc_tab_run_huffbits[6] = { + 5, 1, 2, 3, 4, 5 +}; + +// values in this table range from -1..19; adjust retrieved value by -1 +static const uint16 vlc_tab_tone_level_idx_hi1_huffcodes[20] = { + 0x5714, 0x000c, 0x0002, 0x0001, 0x0000, 0x0004, 0x0034, 0x0054, + 0x0094, 0x0014, 0x0114, 0x0214, 0x0314, 0x0614, 0x0e14, 0x0f14, + 0x2714, 0x0714, 0x1714, 0x3714 +}; + +static const byte vlc_tab_tone_level_idx_hi1_huffbits[20] = { + 15, 4, 2, 1, 3, 5, 6, 7, 8, 10, 10, 11, 11, 12, 12, 12, 14, 14, 15, 14 +}; + +// values in this table range from -1..23; adjust retrieved value by -1 +static const uint16 vlc_tab_tone_level_idx_mid_huffcodes[24] = { + 0x0fea, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x03ea, 0x00ea, 0x002a, 0x001a, + 0x0006, 0x0001, 0x0000, 0x0002, 0x000a, 0x006a, 0x01ea, 0x07ea +}; + +static const byte vlc_tab_tone_level_idx_mid_huffbits[24] = { + 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 7, 5, 3, 1, 2, 4, 6, 8, 10, 12 +}; + +// values in this table range from -1..23; adjust retrieved value by -1 +static const uint16 vlc_tab_tone_level_idx_hi2_huffcodes[24] = { + 0x0664, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0064, 0x00e4, + 0x00a4, 0x0068, 0x0004, 0x0008, 0x0014, 0x0018, 0x0000, 0x0001, + 0x0002, 0x0003, 0x000c, 0x0028, 0x0024, 0x0164, 0x0000, 0x0264 +}; + +static const byte vlc_tab_tone_level_idx_hi2_huffbits[24] = { + 11, 0, 0, 0, 0, 0, 10, 8, 8, 7, 6, 6, 5, 5, 4, 2, 2, 2, 4, 7, 8, 9, 0, 11 +}; + +// values in this table range from -1..8; adjust retrieved value by -1 +static const byte vlc_tab_type30_huffcodes[9] = { + 0x3c, 0x06, 0x00, 0x01, 0x03, 0x02, 0x04, 0x0c, 0x1c +}; + +static const byte vlc_tab_type30_huffbits[9] = { + 6, 3, 3, 2, 2, 3, 4, 5, 6 +}; + +// values in this table range from -1..9; adjust retrieved value by -1 +static const byte vlc_tab_type34_huffcodes[10] = { + 0x18, 0x00, 0x01, 0x04, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08 +}; + +static const byte vlc_tab_type34_huffbits[10] = { + 5, 4, 3, 3, 3, 3, 3, 3, 3, 5 +}; + +// values in this table range from -1..22; adjust retrieved value by -1 +static const uint16 vlc_tab_fft_tone_offset_0_huffcodes[23] = { + 0x038e, 0x0001, 0x0000, 0x0022, 0x000a, 0x0006, 0x0012, 0x0002, + 0x001e, 0x003e, 0x0056, 0x0016, 0x000e, 0x0032, 0x0072, 0x0042, + 0x008e, 0x004e, 0x00f2, 0x002e, 0x0036, 0x00c2, 0x018e +}; + +static const byte vlc_tab_fft_tone_offset_0_huffbits[23] = { + 10, 1, 2, 6, 4, 5, 6, 7, 6, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 6, 6, 8, 10 +}; + +// values in this table range from -1..27; adjust retrieved value by -1 +static const uint16 vlc_tab_fft_tone_offset_1_huffcodes[28] = { + 0x07a4, 0x0001, 0x0020, 0x0012, 0x001c, 0x0008, 0x0006, 0x0010, + 0x0000, 0x0014, 0x0004, 0x0032, 0x0070, 0x000c, 0x0002, 0x003a, + 0x001a, 0x002c, 0x002a, 0x0022, 0x0024, 0x000a, 0x0064, 0x0030, + 0x0062, 0x00a4, 0x01a4, 0x03a4 +}; + +static const byte vlc_tab_fft_tone_offset_1_huffbits[28] = { + 11, 1, 6, 6, 5, 4, 3, 6, 6, 5, 6, 6, 7, 6, 6, 6, + 6, 6, 6, 7, 8, 6, 7, 7, 7, 9, 10, 11 +}; + +// values in this table range from -1..31; adjust retrieved value by -1 +static const uint16 vlc_tab_fft_tone_offset_2_huffcodes[32] = { + 0x1760, 0x0001, 0x0000, 0x0082, 0x000c, 0x0006, 0x0003, 0x0007, + 0x0008, 0x0004, 0x0010, 0x0012, 0x0022, 0x001a, 0x0000, 0x0020, + 0x000a, 0x0040, 0x004a, 0x006a, 0x002a, 0x0042, 0x0002, 0x0060, + 0x00aa, 0x00e0, 0x00c2, 0x01c2, 0x0160, 0x0360, 0x0760, 0x0f60 +}; + +static const byte vlc_tab_fft_tone_offset_2_huffbits[32] = { + 13, 2, 0, 8, 4, 3, 3, 3, 4, 4, 5, 5, 6, 5, 7, 7, + 7, 7, 7, 7, 8, 8, 8, 9, 8, 8, 9, 9, 10, 11, 13, 12 +}; + +// values in this table range from -1..34; adjust retrieved value by -1 +static const uint16 vlc_tab_fft_tone_offset_3_huffcodes[35] = { + 0x33ea, 0x0005, 0x0000, 0x000c, 0x0000, 0x0006, 0x0003, 0x0008, + 0x0002, 0x0001, 0x0004, 0x0007, 0x001a, 0x000f, 0x001c, 0x002c, + 0x000a, 0x001d, 0x002d, 0x002a, 0x000d, 0x004c, 0x008c, 0x006a, + 0x00cd, 0x004d, 0x00ea, 0x020c, 0x030c, 0x010c, 0x01ea, 0x07ea, + 0x0bea, 0x03ea, 0x13ea +}; + +static const byte vlc_tab_fft_tone_offset_3_huffbits[35] = { + 14, 4, 0, 10, 4, 3, 3, 4, 4, 3, 4, 4, 5, 4, 5, 6, + 6, 5, 6, 7, 7, 7, 8, 8, 8, 8, 9, 10, 10, 10, 10, 11, + 12, 13, 14 +}; + +// values in this table range from -1..37; adjust retrieved value by -1 +static const uint16 vlc_tab_fft_tone_offset_4_huffcodes[38] = { + 0x5282, 0x0016, 0x0000, 0x0136, 0x0004, 0x0000, 0x0007, 0x000a, + 0x000e, 0x0003, 0x0001, 0x000d, 0x0006, 0x0009, 0x0012, 0x0005, + 0x0025, 0x0022, 0x0015, 0x0002, 0x0076, 0x0035, 0x0042, 0x00c2, + 0x0182, 0x00b6, 0x0036, 0x03c2, 0x0482, 0x01c2, 0x0682, 0x0882, + 0x0a82, 0x0082, 0x0282, 0x1282, 0x3282, 0x2282 +}; + +static const byte vlc_tab_fft_tone_offset_4_huffbits[38] = { + 15, 6, 0, 9, 3, 3, 3, 4, 4, 3, 4, 4, 5, 4, 5, 6, + 6, 6, 6, 8, 7, 6, 8, 9, 9, 8, 9, 10, 11, 10, 11, 12, + 12, 12, 14, 15, 14, 14 +}; + +/// FFT TABLES + +// values in this table range from -1..27; adjust retrieved value by -1 +static const uint16 fft_level_exp_alt_huffcodes[28] = { + 0x1ec6, 0x0006, 0x00c2, 0x0142, 0x0242, 0x0246, 0x00c6, 0x0046, + 0x0042, 0x0146, 0x00a2, 0x0062, 0x0026, 0x0016, 0x000e, 0x0005, + 0x0004, 0x0003, 0x0000, 0x0001, 0x000a, 0x0012, 0x0002, 0x0022, + 0x01c6, 0x02c6, 0x06c6, 0x0ec6 +}; + +static const byte fft_level_exp_alt_huffbits[28] = { + 13, 7, 8, 9, 10, 10, 10, 10, 10, 9, 8, 7, 6, 5, 4, 3, + 3, 2, 3, 3, 4, 5, 7, 8, 9, 11, 12, 13 +}; + +// values in this table range from -1..19; adjust retrieved value by -1 +static const uint16 fft_level_exp_huffcodes[20] = { + 0x0f24, 0x0001, 0x0002, 0x0000, 0x0006, 0x0005, 0x0007, 0x000c, + 0x000b, 0x0014, 0x0013, 0x0004, 0x0003, 0x0023, 0x0064, 0x00a4, + 0x0024, 0x0124, 0x0324, 0x0724 +}; + +static const byte fft_level_exp_huffbits[20] = { + 12, 3, 3, 3, 3, 3, 3, 4, 4, 5, 5, 6, 6, 6, 7, 8, 9, 10, 11, 12 +}; + +// values in this table range from -1..6; adjust retrieved value by -1 +static const byte fft_stereo_exp_huffcodes[7] = { + 0x3e, 0x01, 0x00, 0x02, 0x06, 0x0e, 0x1e +}; + +static const byte fft_stereo_exp_huffbits[7] = { + 6, 1, 2, 3, 4, 5, 6 +}; + +// values in this table range from -1..8; adjust retrieved value by -1 +static const byte fft_stereo_phase_huffcodes[9] = { + 0x35, 0x02, 0x00, 0x01, 0x0d, 0x15, 0x05, 0x09, 0x03 +}; + +static const byte fft_stereo_phase_huffbits[9] = { + 6, 2, 2, 4, 4, 6, 5, 4, 2 +}; + +static const int fft_cutoff_index_table[4][2] = { + { 1, 2 }, {-1, 0 }, {-1,-2 }, { 0, 0 } +}; + +static const int16 fft_level_index_table[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +}; + +static const byte last_coeff[3] = { + 4, 7, 10 +}; + +static const byte coeff_per_sb_for_avg[3][30] = { + { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, + { 0, 1, 2, 2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 }, + { 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9 } +}; + +static const uint32 dequant_table[3][10][30] = { + { { 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 256, 256, 205, 154, 102, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 51, 102, 154, 205, 256, 238, 219, 201, 183, 165, 146, 128, 110, 91, 73, 55, 37, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 18, 37, 55, 73, 91, 110, 128, 146, 165, 183, 201, 219, 238, 256, 228, 199, 171, 142, 114, 85, 57, 28 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, + { { 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 256, 171, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 85, 171, 256, 171, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 85, 171, 256, 219, 183, 146, 110, 73, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 73, 110, 146, 183, 219, 256, 228, 199, 171, 142, 114, 85, 57, 28, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 57, 85, 114, 142, 171, 199, 228, 256, 213, 171, 128, 85, 43 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, + { { 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 256, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 256, 171, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 85, 171, 256, 192, 128, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 128, 192, 256, 205, 154, 102, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 102, 154, 205, 256, 213, 171, 128, 85, 43, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 85, 128, 171, 213, 256, 213, 171, 128, 85, 43 } } +}; + +static const byte coeff_per_sb_for_dequant[3][30] = { + { 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, + { 0, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6 }, + { 0, 1, 2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9 } +}; + +// first index is subband, 2nd index is 0, 1 or 3 (2 is unused) +static const int8 tone_level_idx_offset_table[30][4] = { + { -50, -50, 0, -50 }, + { -50, -50, 0, -50 }, + { -50, -9, 0, -19 }, + { -16, -6, 0, -12 }, + { -11, -4, 0, -8 }, + { -8, -3, 0, -6 }, + { -7, -3, 0, -5 }, + { -6, -2, 0, -4 }, + { -5, -2, 0, -3 }, + { -4, -1, 0, -3 }, + { -4, -1, 0, -2 }, + { -3, -1, 0, -2 }, + { -3, -1, 0, -2 }, + { -3, -1, 0, -2 }, + { -2, -1, 0, -1 }, + { -2, -1, 0, -1 }, + { -2, -1, 0, -1 }, + { -2, 0, 0, -1 }, + { -2, 0, 0, -1 }, + { -1, 0, 0, -1 }, + { -1, 0, 0, -1 }, + { -1, 0, 0, -1 }, + { -1, 0, 0, -1 }, + { -1, 0, 0, -1 }, + { -1, 0, 0, -1 }, + { -1, 0, 0, -1 }, + { -1, 0, 0, 0 }, + { -1, 0, 0, 0 }, + { -1, 0, 0, 0 }, + { -1, 0, 0, 0 } +}; + +/* all my samples have 1st index 0 or 1 */ +/* second index is subband, only indexes 0-29 seem to be used */ +static const int8 coding_method_table[5][30] = { + { 34, 30, 24, 24, 16, 16, 16, 16, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 + }, + { 34, 30, 24, 24, 16, 16, 16, 16, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 + }, + { 34, 30, 30, 30, 24, 24, 16, 16, 16, 16, 16, 16, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 + }, + { 34, 34, 30, 30, 24, 24, 24, 24, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 10, 10, 10, 10, 10, 10, 10, 10 + }, + { 34, 34, 30, 30, 30, 30, 30, 30, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 + }, +}; + +static const int vlc_stage3_values[60] = { + 0, 1, 2, 3, 4, 6, 8, 10, 12, 16, 20, 24, + 28, 36, 44, 52, 60, 76, 92, 108, 124, 156, 188, 220, + 252, 316, 380, 444, 508, 636, 764, 892, 1020, 1276, 1532, 1788, + 2044, 2556, 3068, 3580, 4092, 5116, 6140, 7164, 8188, 10236, 12284, 14332, + 16380, 20476, 24572, 28668, 32764, 40956, 49148, 57340, 65532, 81916, 98300,114684 +}; + +static const float fft_tone_sample_table[4][16][5] = { + { { .0100000000f,-.0037037037f,-.0020000000f,-.0069444444f,-.0018416207f }, + { .0416666667f, .0000000000f, .0000000000f,-.0208333333f,-.0123456791f }, + { .1250000000f, .0558035709f, .0330687836f,-.0164473690f,-.0097465888f }, + { .1562500000f, .0625000000f, .0370370370f,-.0062500000f,-.0037037037f }, + { .1996007860f, .0781250000f, .0462962948f, .0022727272f, .0013468013f }, + { .2000000000f, .0625000000f, .0370370373f, .0208333333f, .0074074073f }, + { .2127659619f, .0555555556f, .0329218097f, .0208333333f, .0123456791f }, + { .2173913121f, .0473484844f, .0280583613f, .0347222239f, .0205761325f }, + { .2173913121f, .0347222239f, .0205761325f, .0473484844f, .0280583613f }, + { .2127659619f, .0208333333f, .0123456791f, .0555555556f, .0329218097f }, + { .2000000000f, .0208333333f, .0074074073f, .0625000000f, .0370370370f }, + { .1996007860f, .0022727272f, .0013468013f, .0781250000f, .0462962948f }, + { .1562500000f,-.0062500000f,-.0037037037f, .0625000000f, .0370370370f }, + { .1250000000f,-.0164473690f,-.0097465888f, .0558035709f, .0330687836f }, + { .0416666667f,-.0208333333f,-.0123456791f, .0000000000f, .0000000000f }, + { .0100000000f,-.0069444444f,-.0018416207f,-.0037037037f,-.0020000000f } }, + + { { .0050000000f,-.0200000000f, .0125000000f,-.3030303030f, .0020000000f }, + { .1041666642f, .0400000000f,-.0250000000f, .0333333333f,-.0200000000f }, + { .1250000000f, .0100000000f, .0142857144f,-.0500000007f,-.0200000000f }, + { .1562500000f,-.0006250000f,-.00049382716f,-.000625000f,-.00049382716f }, + { .1562500000f,-.0006250000f,-.00049382716f,-.000625000f,-.00049382716f }, + { .1250000000f,-.0500000000f,-.0200000000f, .0100000000f, .0142857144f }, + { .1041666667f, .0333333333f,-.0200000000f, .0400000000f,-.0250000000f }, + { .0050000000f,-.3030303030f, .0020000001f,-.0200000000f, .0125000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f } }, + + { { .1428571492f, .1250000000f,-.0285714287f,-.0357142873f, .0208333333f }, + { .1818181818f, .0588235296f, .0333333333f, .0212765951f, .0100000000f }, + { .1818181818f, .0212765951f, .0100000000f, .0588235296f, .0333333333f }, + { .1428571492f,-.0357142873f, .0208333333f, .1250000000f,-.0285714287f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f } }, + + { { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f }, + { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f } } +}; + +static const float fft_tone_level_table[2][64] = { { +// pow ~ (i > 46) ? 0 : (((((i & 1) ? 431 : 304) << (i >> 1))) / 1024.0); + 0.17677669f, 0.42677650f, 0.60355347f, 0.85355347f, + 1.20710683f, 1.68359375f, 2.37500000f, 3.36718750f, + 4.75000000f, 6.73437500f, 9.50000000f, 13.4687500f, + 19.0000000f, 26.9375000f, 38.0000000f, 53.8750000f, + 76.0000000f, 107.750000f, 152.000000f, 215.500000f, + 304.000000f, 431.000000f, 608.000000f, 862.000000f, + 1216.00000f, 1724.00000f, 2432.00000f, 3448.00000f, + 4864.00000f, 6896.00000f, 9728.00000f, 13792.0000f, + 19456.0000f, 27584.0000f, 38912.0000f, 55168.0000f, + 77824.0000f, 110336.000f, 155648.000f, 220672.000f, + 311296.000f, 441344.000f, 622592.000f, 882688.000f, + 1245184.00f, 1765376.00f, 2490368.00f, 0.00000000f, + 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f, + 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f, + 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f, + 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f, + }, { +// pow = (i > 45) ? 0 : ((((i & 1) ? 431 : 304) << (i >> 1)) / 512.0); + 0.59375000f, 0.84179688f, 1.18750000f, 1.68359375f, + 2.37500000f, 3.36718750f, 4.75000000f, 6.73437500f, + 9.50000000f, 13.4687500f, 19.0000000f, 26.9375000f, + 38.0000000f, 53.8750000f, 76.0000000f, 107.750000f, + 152.000000f, 215.500000f, 304.000000f, 431.000000f, + 608.000000f, 862.000000f, 1216.00000f, 1724.00000f, + 2432.00000f, 3448.00000f, 4864.00000f, 6896.00000f, + 9728.00000f, 13792.0000f, 19456.0000f, 27584.0000f, + 38912.0000f, 55168.0000f, 77824.0000f, 110336.000f, + 155648.000f, 220672.000f, 311296.000f, 441344.000f, + 622592.000f, 882688.000f, 1245184.00f, 1765376.00f, + 2490368.00f, 3530752.00f, 0.00000000f, 0.00000000f, + 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f, + 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f, + 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f, + 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f +} }; + +static const float fft_tone_envelope_table[4][31] = { + { .009607375f, .038060248f, .084265202f, .146446645f, .222214907f, .308658302f, + .402454883f, .500000060f, .597545207f, .691341758f, .777785182f, .853553414f, + .915734828f, .961939812f, .990392685f, 1.00000000f, .990392625f, .961939752f, + .915734768f, .853553295f, .777785063f, .691341639f, .597545087f, .500000000f, + .402454853f, .308658272f, .222214878f, .146446615f, .084265172f, .038060218f, + .009607345f }, + { .038060248f, .146446645f, .308658302f, .500000060f, .691341758f, .853553414f, + .961939812f, 1.00000000f, .961939752f, .853553295f, .691341639f, .500000000f, + .308658272f, .146446615f, .038060218f, .000000000f, .000000000f, .000000000f, + .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, + .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, + .000000000f }, + { .146446645f, .500000060f, .853553414f, 1.00000000f, .853553295f, .500000000f, + .146446615f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, + .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, + .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, + .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, + .000000000f }, + { .500000060f, 1.00000000f, .500000000f, .000000000f, .000000000f, .000000000f, + .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, + .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, + .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, + .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, + .000000000f } +}; + +static const float sb_noise_attenuation[32] = { + 0.0f, 0.0f, 0.3f, 0.4f, 0.5f, 0.7f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, +}; + +static const byte fft_subpackets[32] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0 +}; + +// first index is joined_stereo, second index is 0 or 2 (1 is unused) +static const float dequant_1bit[2][3] = { + {-0.920000f, 0.000000f, 0.920000f }, + {-0.890000f, 0.000000f, 0.890000f } +}; + +static const float type30_dequant[8] = { + -1.0f,-0.625f,-0.291666656732559f,0.0f, + 0.25f,0.5f,0.75f,1.0f, +}; + +static const float type34_delta[10] = { // FIXME: covers 8 entries.. + -1.0f,-0.60947573184967f,-0.333333343267441f,-0.138071194291115f,0.0f, + 0.138071194291115f,0.333333343267441f,0.60947573184967f,1.0f,0.0f, +}; + +} // End of namespace Mohawk + +#endif diff --git a/engines/mohawk/video/qt_player.cpp b/engines/mohawk/video/qt_player.cpp new file mode 100644 index 0000000000..0ed05bb84d --- /dev/null +++ b/engines/mohawk/video/qt_player.cpp @@ -0,0 +1,1272 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// +// Heavily based on ffmpeg code. +// +// Copyright (c) 2001 Fabrice Bellard. +// First version by Francois Revol revol@free.fr +// Seek function by Gael Chardon gael.dev@4now.net +// + +#include "mohawk/video/qt_player.h" + +#include "common/debug.h" +#include "common/endian.h" +#include "common/util.h" +#include "common/zlib.h" + +// Audio codecs +#include "sound/decoders/adpcm.h" +#include "sound/decoders/raw.h" +#include "mohawk/video/qdm2.h" + +// Video codecs +#include "mohawk/jpeg.h" +#include "mohawk/video/cinepak.h" +#include "mohawk/video/qtrle.h" +#include "mohawk/video/rpza.h" +#include "mohawk/video/smc.h" + +namespace Mohawk { + +//////////////////////////////////////////// +// QTPlayer +//////////////////////////////////////////// + +QTPlayer::QTPlayer() : Graphics::VideoDecoder() { + _audStream = NULL; + _beginOffset = 0; + _videoCodec = NULL; + _curFrame = -1; + _startTime = _nextFrameStartTime = 0; + _audHandle = Audio::SoundHandle(); + _numStreams = 0; + _fd = 0; + _scaledSurface = 0; + _dirtyPalette = false; +} + +QTPlayer::~QTPlayer() { + close(); +} + +uint16 QTPlayer::getWidth() const { + if (_videoStreamIndex < 0) + return 0; + + return _streams[_videoStreamIndex]->width / getScaleMode(); +} + +uint16 QTPlayer::getHeight() const { + if (_videoStreamIndex < 0) + return 0; + + return _streams[_videoStreamIndex]->height / getScaleMode(); +} + +uint32 QTPlayer::getFrameCount() const { + if (_videoStreamIndex < 0) + return 0; + + return _streams[_videoStreamIndex]->nb_frames; +} + +byte QTPlayer::getBitsPerPixel() { + if (_videoStreamIndex < 0) + return 0; + + return _streams[_videoStreamIndex]->bits_per_sample & 0x1F; +} + +uint32 QTPlayer::getCodecTag() { + if (_videoStreamIndex < 0) + return 0; + + return _streams[_videoStreamIndex]->codec_tag; +} + +ScaleMode QTPlayer::getScaleMode() const { + if (_videoStreamIndex < 0) + return kScaleNormal; + + return (ScaleMode)(_scaleMode * _streams[_videoStreamIndex]->scaleMode); +} + +uint32 QTPlayer::getFrameDuration() { + if (_videoStreamIndex < 0) + return 0; + + uint32 curFrameIndex = 0; + for (int32 i = 0; i < _streams[_videoStreamIndex]->stts_count; i++) { + curFrameIndex += _streams[_videoStreamIndex]->stts_data[i].count; + if ((uint32)_curFrame < curFrameIndex) { + // Ok, now we have what duration this frame has. + return _streams[_videoStreamIndex]->stts_data[i].duration; + } + } + + // This should never occur + error ("Cannot find duration for frame %d", _curFrame); + return 0; +} + +Graphics::PixelFormat QTPlayer::getPixelFormat() const { + if (!_videoCodec) + return Graphics::PixelFormat::createFormatCLUT8(); + + return _videoCodec->getPixelFormat(); +} + +void QTPlayer::rewind() { + delete _videoCodec; _videoCodec = NULL; + _curFrame = -1; + _startTime = _nextFrameStartTime = 0; + + // Restart the audio too + stopAudio(); + if (_audioStreamIndex >= 0) { + _curAudioChunk = 0; + _audStream = Audio::makeQueuingAudioStream(_streams[_audioStreamIndex]->sample_rate, _streams[_audioStreamIndex]->channels == 2); + } + startAudio(); +} + +Graphics::Codec *QTPlayer::createCodec(uint32 codecTag, byte bitsPerPixel) { + if (codecTag == MKID_BE('cvid')) { + // Cinepak: As used by most Myst and all Riven videos as well as some Myst ME videos. "The Chief" videos also use this. + return new CinepakDecoder(); + } else if (codecTag == MKID_BE('rpza')) { + // Apple Video ("Road Pizza"): Used by some Myst videos. + return new RPZADecoder(getWidth(), getHeight()); + } else if (codecTag == MKID_BE('rle ')) { + // QuickTime RLE: Used by some Myst ME videos. + return new QTRLEDecoder(getWidth(), getHeight(), bitsPerPixel); + } else if (codecTag == MKID_BE('smc ')) { + // Apple SMC: Used by some Myst videos. + return new SMCDecoder(getWidth(), getHeight()); + } else if (codecTag == MKID_BE('SVQ1')) { + // Sorenson Video 1: Used by some Myst ME videos. + warning ("Sorenson Video 1 not yet supported"); + } else if (codecTag == MKID_BE('SVQ3')) { + // Sorenson Video 3: Used by some Myst ME videos. + warning ("Sorenson Video 3 not yet supported"); + } else if (codecTag == MKID_BE('jpeg')) { + // Motion JPEG: Used by some Myst ME 10th Anniversary videos. + return new JPEGDecoder(true); + } else if (codecTag == MKID_BE('QkBk')) { + // CDToons: Used by most of the Broderbund games. This is an unknown format so far. + warning ("CDToons not yet supported"); + } else { + warning ("Unsupported codec \'%s\'", tag2str(codecTag)); + } + + return NULL; +} + +void QTPlayer::startAudio() { + if (_audStream) // No audio/audio not supported + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_audHandle, _audStream); +} + +void QTPlayer::stopAudio() { + if (_audStream) { + g_system->getMixer()->stopHandle(_audHandle); + _audStream = NULL; // the mixer automatically frees the stream + } +} + +void QTPlayer::pauseVideoIntern(bool pause) { + if (_audStream) + g_system->getMixer()->pauseHandle(_audHandle, pause); +} + +Graphics::Surface *QTPlayer::decodeNextFrame() { + if (!_videoCodec || _curFrame >= (int32)getFrameCount() - 1) + return NULL; + + if (_startTime == 0) + _startTime = g_system->getMillis(); + + _curFrame++; + _nextFrameStartTime += getFrameDuration(); + + Common::SeekableReadStream *frameData = getNextFramePacket(); + + if (frameData) { + Graphics::Surface *frame = _videoCodec->decodeImage(frameData); + delete frameData; + return scaleSurface(frame); + } + + return NULL; +} + +Graphics::Surface *QTPlayer::scaleSurface(Graphics::Surface *frame) { + if (getScaleMode() == kScaleNormal) + return frame; + + assert(_scaledSurface); + + for (uint32 j = 0; j < _scaledSurface->h; j++) + for (uint32 k = 0; k < _scaledSurface->w; k++) + memcpy(_scaledSurface->getBasePtr(k, j), frame->getBasePtr(k * getScaleMode(), j * getScaleMode()), frame->bytesPerPixel); + + return _scaledSurface; +} + +bool QTPlayer::endOfVideo() const { + return (!_audStream || _audStream->endOfData()) && (!_videoCodec || _curFrame >= (int32)getFrameCount() - 1); +} + +bool QTPlayer::needsUpdate() const { + return !endOfVideo() && getTimeToNextFrame() == 0; +} + +uint32 QTPlayer::getElapsedTime() const { + if (_audStream) + return g_system->getMixer()->getSoundElapsedTime(_audHandle); + + return g_system->getMillis() - _startTime; +} + +uint32 QTPlayer::getTimeToNextFrame() const { + if (endOfVideo() || _curFrame < 0) + return 0; + + // Convert from the Sega FILM base to 1000 + uint32 nextFrameStartTime = _nextFrameStartTime * 1000 / _streams[_videoStreamIndex]->time_scale; + uint32 elapsedTime = getElapsedTime(); + + if (nextFrameStartTime <= elapsedTime) + return 0; + + return nextFrameStartTime - elapsedTime; +} + +bool QTPlayer::load(Common::SeekableReadStream &stream) { + _fd = &stream; + _foundMOOV = _foundMDAT = false; + _numStreams = 0; + _partial = 0; + _videoStreamIndex = _audioStreamIndex = -1; + _startTime = 0; + + initParseTable(); + + MOVatom atom = { 0, 0, 0xffffffff }; + + if (readDefault(atom) < 0 || (!_foundMOOV && !_foundMDAT)) + return false; + + debug(0, "on_parse_exit_offset=%d", _fd->pos()); + + // some cleanup : make sure we are on the mdat atom + if((uint32)_fd->pos() != _mdatOffset) + _fd->seek(_mdatOffset, SEEK_SET); + + _next_chunk_offset = _mdatOffset; // initialise reading + + for (uint32 i = 0; i < _numStreams;) { + if (_streams[i]->codec_type == CODEC_TYPE_MOV_OTHER) {// not audio, not video, delete + delete _streams[i]; + for (uint32 j = i + 1; j < _numStreams; j++) + _streams[j - 1] = _streams[j]; + _numStreams--; + } else + i++; + } + + for (uint32 i = 0; i < _numStreams; i++) { + MOVStreamContext *sc = _streams[i]; + + if(!sc->time_rate) + sc->time_rate = 1; + + if(!sc->time_scale) + sc->time_scale = _timeScale; + + //av_set_pts_info(s->streams[i], 64, sc->time_rate, sc->time_scale); + + sc->duration /= sc->time_rate; + + sc->ffindex = i; + sc->is_ff_stream = 1; + + if (sc->codec_type == CODEC_TYPE_VIDEO && _videoStreamIndex < 0) + _videoStreamIndex = i; + else if (sc->codec_type == CODEC_TYPE_AUDIO && _audioStreamIndex < 0) + _audioStreamIndex = i; + } + + if (_audioStreamIndex >= 0 && checkAudioCodecSupport(_streams[_audioStreamIndex]->codec_tag)) { + _audStream = Audio::makeQueuingAudioStream(_streams[_audioStreamIndex]->sample_rate, _streams[_audioStreamIndex]->channels == 2); + _curAudioChunk = 0; + + // Make sure the bits per sample transfers to the sample size + if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('raw ') || _streams[_audioStreamIndex]->codec_tag == MKID_BE('twos')) + _streams[_audioStreamIndex]->sample_size = (_streams[_audioStreamIndex]->bits_per_sample / 8) * _streams[_audioStreamIndex]->channels; + + startAudio(); + } + + if (_videoStreamIndex >= 0) { + _videoCodec = createCodec(getCodecTag(), getBitsPerPixel()); + + if (getScaleMode() != kScaleNormal) { + // We have to initialize the scaled surface + _scaledSurface = new Graphics::Surface(); + _scaledSurface->create(getWidth(), getHeight(), getPixelFormat().bytesPerPixel); + } + } + + return true; +} + +void QTPlayer::initParseTable() { + static const ParseTable p[] = { + { MKID_BE('dinf'), &QTPlayer::readDefault }, + { MKID_BE('dref'), &QTPlayer::readLeaf }, + { MKID_BE('edts'), &QTPlayer::readDefault }, + { MKID_BE('elst'), &QTPlayer::readELST }, + { MKID_BE('hdlr'), &QTPlayer::readHDLR }, + { MKID_BE('mdat'), &QTPlayer::readMDAT }, + { MKID_BE('mdhd'), &QTPlayer::readMDHD }, + { MKID_BE('mdia'), &QTPlayer::readDefault }, + { MKID_BE('minf'), &QTPlayer::readDefault }, + { MKID_BE('moov'), &QTPlayer::readMOOV }, + { MKID_BE('mvhd'), &QTPlayer::readMVHD }, + { MKID_BE('smhd'), &QTPlayer::readLeaf }, + { MKID_BE('stbl'), &QTPlayer::readDefault }, + { MKID_BE('stco'), &QTPlayer::readSTCO }, + { MKID_BE('stsc'), &QTPlayer::readSTSC }, + { MKID_BE('stsd'), &QTPlayer::readSTSD }, + { MKID_BE('stss'), &QTPlayer::readSTSS }, + { MKID_BE('stsz'), &QTPlayer::readSTSZ }, + { MKID_BE('stts'), &QTPlayer::readSTTS }, + { MKID_BE('tkhd'), &QTPlayer::readTKHD }, + { MKID_BE('trak'), &QTPlayer::readTRAK }, + { MKID_BE('udta'), &QTPlayer::readLeaf }, + { MKID_BE('vmhd'), &QTPlayer::readLeaf }, + { MKID_BE('cmov'), &QTPlayer::readCMOV }, + { MKID_BE('wave'), &QTPlayer::readWAVE }, + { 0, 0 } + }; + + _parseTable = p; +} + +int QTPlayer::readDefault(MOVatom atom) { + uint32 total_size = 0; + MOVatom a; + int err = 0; + + a.offset = atom.offset; + + while(((total_size + 8) < atom.size) && !_fd->eos() && !err) { + a.size = atom.size; + a.type = 0; + + if (atom.size >= 8) { + a.size = _fd->readUint32BE(); + a.type = _fd->readUint32BE(); + } + + total_size += 8; + a.offset += 8; + debug(4, "type: %08x %.4s sz: %x %x %x", a.type, tag2str(a.type), a.size, atom.size, total_size); + + if (a.size == 1) { // 64 bit extended size + warning("64 bit extended size is not supported in QuickTime"); + return -1; + } + + if (a.size == 0) { + a.size = atom.size - total_size; + if (a.size <= 8) + break; + } + + uint32 i = 0; + + for (; _parseTable[i].type != 0 && _parseTable[i].type != a.type; i++) + // empty; + + if (a.size < 8) + break; + + a.size -= 8; + + if (_parseTable[i].type == 0) { // skip leaf atoms data + debug(0, ">>> Skipped [%s]", tag2str(a.type)); + + _fd->seek(a.size, SEEK_CUR); + } else { + uint32 start_pos = _fd->pos(); + err = (this->*_parseTable[i].func)(a); + + uint32 left = a.size - _fd->pos() + start_pos; + + if (left > 0) // skip garbage at atom end + _fd->seek(left, SEEK_CUR); + } + + a.offset += a.size; + total_size += a.size; + } + + if (!err && total_size < atom.size) + _fd->seek(atom.size - total_size, SEEK_SET); + + return err; +} + +int QTPlayer::readLeaf(MOVatom atom) { + if (atom.size > 1) + _fd->seek(atom.size, SEEK_SET); + + return 0; +} + +int QTPlayer::readMOOV(MOVatom atom) { + if (readDefault(atom) < 0) + return -1; + + // we parsed the 'moov' atom, we can terminate the parsing as soon as we find the 'mdat' + // so we don't parse the whole file if over a network + _foundMOOV = true; + + if(_foundMDAT) + return 1; // found both, just go + + return 0; // now go for mdat +} + +int QTPlayer::readCMOV(MOVatom atom) { +#ifdef USE_ZLIB + // Read in the dcom atom + _fd->readUint32BE(); + if (_fd->readUint32BE() != MKID_BE('dcom')) + return -1; + if (_fd->readUint32BE() != MKID_BE('zlib')) { + warning("Unknown cmov compression type"); + return -1; + } + + // Read in the cmvd atom + uint32 compressedSize = _fd->readUint32BE() - 12; + if (_fd->readUint32BE() != MKID_BE('cmvd')) + return -1; + uint32 uncompressedSize = _fd->readUint32BE(); + + // Read in data + byte *compressedData = (byte *)malloc(compressedSize); + _fd->read(compressedData, compressedSize); + + // Create uncompressed stream + byte *uncompressedData = (byte *)malloc(uncompressedSize); + + // Uncompress the data + unsigned long dstLen = uncompressedSize; + if (!Common::uncompress(uncompressedData, &dstLen, compressedData, compressedSize)) { + warning ("Could not uncompress cmov chunk"); + return -1; + } + + // Load data into a new MemoryReadStream and assign _fd to be that + Common::SeekableReadStream *oldStream = _fd; + _fd = new Common::MemoryReadStream(uncompressedData, uncompressedSize, DisposeAfterUse::YES); + + // Read the contents of the uncompressed data + MOVatom a = { MKID_BE('moov'), 0, uncompressedSize }; + int err = readDefault(a); + + // Assign the file handle back to the original handle + free(compressedData); + delete _fd; + _fd = oldStream; + + return err; +#else + warning ("zlib not found, cannot read QuickTime cmov atom"); + return -1; +#endif +} + +int QTPlayer::readMVHD(MOVatom atom) { + byte version = _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + if (version == 1) { + warning("QuickTime version 1"); + _fd->readUint32BE(); _fd->readUint32BE(); + _fd->readUint32BE(); _fd->readUint32BE(); + } else { + _fd->readUint32BE(); // creation time + _fd->readUint32BE(); // modification time + } + + _timeScale = _fd->readUint32BE(); // time scale + debug(0, "time scale = %i\n", _timeScale); + + // duration + _duration = (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); + _fd->readUint32BE(); // preferred scale + + _fd->readUint16BE(); // preferred volume + + _fd->seek(10, SEEK_CUR); // reserved + + // We only need two values from the movie display matrix. Most of the values are just + // skipped. xMod and yMod are 16:16 fixed point numbers, the last part of the 3x3 matrix + // is 2:30. + uint32 xMod = _fd->readUint32BE(); + _fd->skip(12); + uint32 yMod = _fd->readUint32BE(); + _fd->skip(16); + + if (xMod != yMod) + error("X and Y resolution modifiers differ"); + + if (xMod == 0x8000) + _scaleMode = kScaleHalf; + else if (xMod == 0x4000) + _scaleMode = kScaleQuarter; + else + _scaleMode = kScaleNormal; + + debug(1, "readMVHD(): scaleMode = %d", (int)_scaleMode); + + _fd->readUint32BE(); // preview time + _fd->readUint32BE(); // preview duration + _fd->readUint32BE(); // poster time + _fd->readUint32BE(); // selection time + _fd->readUint32BE(); // selection duration + _fd->readUint32BE(); // current time + _fd->readUint32BE(); // next track ID + + return 0; +} + +int QTPlayer::readTRAK(MOVatom atom) { + MOVStreamContext *sc = new MOVStreamContext(); + + if (!sc) + return -1; + + sc->sample_to_chunk_index = -1; + sc->codec_type = CODEC_TYPE_MOV_OTHER; + sc->start_time = 0; // XXX: check + _streams[_numStreams++] = sc; + + return readDefault(atom); +} + +// this atom contains actual media data +int QTPlayer::readMDAT(MOVatom atom) { + if (atom.size == 0) // wrong one (MP4) + return 0; + + _foundMDAT = true; + + _mdatOffset = atom.offset; + _mdatSize = atom.size; + + if (_foundMOOV) + return 1; // found both, just go + + _fd->seek(atom.size, SEEK_CUR); + + return 0; // now go for moov +} + +int QTPlayer::readTKHD(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + byte version = _fd->readByte(); + + _fd->readByte(); _fd->readByte(); + _fd->readByte(); // flags + // + //MOV_TRACK_ENABLED 0x0001 + //MOV_TRACK_IN_MOVIE 0x0002 + //MOV_TRACK_IN_PREVIEW 0x0004 + //MOV_TRACK_IN_POSTER 0x0008 + // + + if (version == 1) { + _fd->readUint32BE(); _fd->readUint32BE(); + _fd->readUint32BE(); _fd->readUint32BE(); + } else { + _fd->readUint32BE(); // creation time + _fd->readUint32BE(); // modification time + } + + /* st->id = */_fd->readUint32BE(); // track id (NOT 0 !) + _fd->readUint32BE(); // reserved + //st->start_time = 0; // check + (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); // highlevel (considering edits) duration in movie timebase + _fd->readUint32BE(); // reserved + _fd->readUint32BE(); // reserved + + _fd->readUint16BE(); // layer + _fd->readUint16BE(); // alternate group + _fd->readUint16BE(); // volume + _fd->readUint16BE(); // reserved + + // We only need the two values from the displacement matrix for a track. + // See readMVHD() for more information. + uint32 xMod = _fd->readUint32BE(); + _fd->skip(12); + uint32 yMod = _fd->readUint32BE(); + _fd->skip(16); + + if (xMod != yMod) + error("X and Y resolution modifiers differ"); + + if (xMod == 0x8000) + st->scaleMode = kScaleHalf; + else if (xMod == 0x4000) + st->scaleMode = kScaleQuarter; + else + st->scaleMode = kScaleNormal; + + debug(1, "readTKHD(): scaleMode = %d", (int)_scaleMode); + + // these are fixed-point, 16:16 + // uint32 tkWidth = _fd->readUint32BE() >> 16; // track width + // uint32 tkHeight = _fd->readUint32BE() >> 16; // track height + + return 0; +} + +// edit list atom +int QTPlayer::readELST(MOVatom atom) { + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + uint32 editCount = _streams[_numStreams - 1]->edit_count = _fd->readUint32BE(); // entries + + for (uint32 i = 0; i < editCount; i++){ + _fd->readUint32BE(); // Track duration + _fd->readUint32BE(); // Media time + _fd->readUint32BE(); // Media rate + } + + debug(0, "track[%i].edit_count = %i", _numStreams - 1, _streams[_numStreams - 1]->edit_count); + + if (editCount != 1) + warning("Multiple edit list entries. Things may go awry"); + + return 0; +} + +int QTPlayer::readHDLR(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + // component type + uint32 ctype = _fd->readUint32LE(); + uint32 type = _fd->readUint32BE(); // component subtype + + debug(0, "ctype= %s (0x%08lx)", tag2str(ctype), (long)ctype); + debug(0, "stype= %s", tag2str(type)); + + if(ctype == MKID_BE('mhlr')) // MOV + debug(0, "MOV detected"); + else if(ctype == 0) { + warning("MP4 streams are not supported"); + return -1; + } + + if (type == MKID_BE('vide')) + st->codec_type = CODEC_TYPE_VIDEO; + else if (type == MKID_BE('soun')) + st->codec_type = CODEC_TYPE_AUDIO; + + _fd->readUint32BE(); // component manufacture + _fd->readUint32BE(); // component flags + _fd->readUint32BE(); // component flags mask + + if (atom.size <= 24) + return 0; // nothing left to read + + // .mov: PASCAL string + byte len = _fd->readByte(); + _fd->seek(len, SEEK_CUR); + + _fd->seek(atom.size - (_fd->pos() - atom.offset), SEEK_CUR); + + return 0; +} + +int QTPlayer::readMDHD(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + byte version = _fd->readByte(); + + if (version > 1) + return 1; // unsupported + + _fd->readByte(); _fd->readByte(); + _fd->readByte(); // flags + + if (version == 1) { + _fd->readUint32BE(); _fd->readUint32BE(); + _fd->readUint32BE(); _fd->readUint32BE(); + } else { + _fd->readUint32BE(); // creation time + _fd->readUint32BE(); // modification time + } + + st->time_scale = _fd->readUint32BE(); + st->duration = (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); // duration + + _fd->readUint16BE(); // language + _fd->readUint16BE(); // quality + + return 0; +} + +int QTPlayer::readSTSD(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + uint32 entries = _fd->readUint32BE(); + + while (entries--) { //Parsing Sample description table + MOVatom a = { 0, 0, 0 }; + uint32 start_pos = _fd->pos(); + int size = _fd->readUint32BE(); // size + uint32 format = _fd->readUint32BE(); // data format + + _fd->readUint32BE(); // reserved + _fd->readUint16BE(); // reserved + _fd->readUint16BE(); // index + + debug(0, "size=%d 4CC= %s codec_type=%d", size, tag2str(format), st->codec_type); + st->codec_tag = format; + + if (st->codec_type == CODEC_TYPE_VIDEO) { + debug(0, "Video Codec FourCC: \'%s\'", tag2str(format)); + + _fd->readUint16BE(); // version + _fd->readUint16BE(); // revision level + _fd->readUint32BE(); // vendor + _fd->readUint32BE(); // temporal quality + _fd->readUint32BE(); // spacial quality + + st->width = _fd->readUint16BE(); // width + st->height = _fd->readUint16BE(); // height + + _fd->readUint32BE(); // horiz resolution + _fd->readUint32BE(); // vert resolution + _fd->readUint32BE(); // data size, always 0 + uint16 frames_per_sample = _fd->readUint16BE(); // frames per samples + + debug(0, "frames/samples = %d", frames_per_sample); + + byte codec_name[32]; + _fd->read(codec_name, 32); // codec name, pascal string (FIXME: true for mp4?) + if (codec_name[0] <= 31) { + memcpy(st->codec_name, &codec_name[1], codec_name[0]); + st->codec_name[codec_name[0]] = 0; + } + + st->bits_per_sample = _fd->readUint16BE(); // depth + st->color_table_id = _fd->readUint16BE(); // colortable id + +// These are set in mov_read_stts and might already be set! +// st->codec->time_base.den = 25; +// st->codec->time_base.num = 1; + + + // figure out the palette situation + byte colorDepth = st->bits_per_sample & 0x1F; + bool colorGreyscale = (st->bits_per_sample & 0x20) != 0; + + debug(0, "color depth: %d", colorDepth); + + // if the depth is 2, 4, or 8 bpp, file is palettized + if (colorDepth == 2 || colorDepth == 4 || colorDepth == 8) { + _dirtyPalette = true; + + if (colorGreyscale) { + debug(0, "Greyscale palette"); + + // compute the greyscale palette + uint16 colorCount = 1 << colorDepth; + int16 colorIndex = 255; + byte colorDec = 256 / (colorCount - 1); + for (byte j = 0; j < colorCount; j++) { + _palette[j * 3] = _palette[j * 3 + 1] = _palette[j * 3 + 2] = colorIndex; + colorIndex -= colorDec; + if (colorIndex < 0) + colorIndex = 0; + } + } else if (st->color_table_id & 0x08) { + // if flag bit 3 is set, use the default palette + //uint16 colorCount = 1 << colorDepth; + + warning("Predefined palette! %dbpp", colorDepth); +#if 0 + byte *color_table; + byte r, g, b; + + if (colorDepth == 2) + color_table = ff_qt_default_palette_4; + else if (colorDepth == 4) + color_table = ff_qt_default_palette_16; + else + color_table = ff_qt_default_palette_256; + + for (byte j = 0; j < color_count; j++) { + r = color_table[j * 4 + 0]; + g = color_table[j * 4 + 1]; + b = color_table[j * 4 + 2]; + _palette_control.palette[j] = (r << 16) | (g << 8) | (b); + } +#endif + + } else { + debug(0, "Palette from file"); + + // load the palette from the file + uint32 colorStart = _fd->readUint32BE(); + /* uint16 colorCount = */ _fd->readUint16BE(); + uint16 colorEnd = _fd->readUint16BE(); + for (uint32 j = colorStart; j <= colorEnd; j++) { + // each R, G, or B component is 16 bits; + // only use the top 8 bits; skip alpha bytes + // up front + _fd->readByte(); + _fd->readByte(); + _palette[j * 3] = _fd->readByte(); + _fd->readByte(); + _palette[j * 3 + 1] = _fd->readByte(); + _fd->readByte(); + _palette[j * 3 + 2] = _fd->readByte(); + _fd->readByte(); + } + } + st->palettized = true; + } else + st->palettized = false; + } else if (st->codec_type == CODEC_TYPE_AUDIO) { + debug(0, "Audio Codec FourCC: \'%s\'", tag2str(format)); + + st->stsd_version = _fd->readUint16BE(); + _fd->readUint16BE(); // revision level + _fd->readUint32BE(); // vendor + + st->channels = _fd->readUint16BE(); // channel count + st->bits_per_sample = _fd->readUint16BE(); // sample size + // do we need to force to 16 for AMR ? + + // handle specific s8 codec + _fd->readUint16BE(); // compression id = 0 + _fd->readUint16BE(); // packet size = 0 + + st->sample_rate = (_fd->readUint32BE() >> 16); + + debug(0, "stsd version =%d", st->stsd_version); + if (st->stsd_version == 0) { + // Not used, except in special cases. See below. + st->samples_per_frame = st->bytes_per_frame = 0; + } else if (st->stsd_version == 1) { + // Read QT version 1 fields. In version 0 these dont exist. + st->samples_per_frame = _fd->readUint32BE(); + debug(0, "stsd samples_per_frame =%d", st->samples_per_frame); + _fd->readUint32BE(); // bytes per packet + st->bytes_per_frame = _fd->readUint32BE(); + debug(0, "stsd bytes_per_frame =%d", st->bytes_per_frame); + _fd->readUint32BE(); // bytes per sample + } else { + warning("Unsupported QuickTime STSD audio version %d", st->stsd_version); + return 1; + } + + // Version 0 videos (such as the Riven ones) don't have this set, + // but we need it later on. Add it in here. + if (format == MKID_BE('ima4')) { + st->samples_per_frame = 64; + st->bytes_per_frame = 34 * st->channels; + } + } else { + // other codec type, just skip (rtp, mp4s, tmcd ...) + _fd->seek(size - (_fd->pos() - start_pos), SEEK_CUR); + } + + // this will read extra atoms at the end (wave, alac, damr, avcC, SMI ...) + a.size = size - (_fd->pos() - start_pos); + if (a.size > 8) + readDefault(a); + else if (a.size > 0) + _fd->seek(a.size, SEEK_CUR); + } + + if (st->codec_type == CODEC_TYPE_AUDIO && st->sample_rate == 0 && st->time_scale > 1) + st->sample_rate= st->time_scale; + + return 0; +} + +int QTPlayer::readSTSC(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->sample_to_chunk_sz = _fd->readUint32BE(); + + debug(0, "track[%i].stsc.entries = %i", _numStreams - 1, st->sample_to_chunk_sz); + + st->sample_to_chunk = new MOVstsc[st->sample_to_chunk_sz]; + + if (!st->sample_to_chunk) + return -1; + + for (uint32 i = 0; i < st->sample_to_chunk_sz; i++) { + st->sample_to_chunk[i].first = _fd->readUint32BE(); + st->sample_to_chunk[i].count = _fd->readUint32BE(); + st->sample_to_chunk[i].id = _fd->readUint32BE(); + //printf ("Sample to Chunk[%d]: First = %d, Count = %d\n", i, st->sample_to_chunk[i].first, st->sample_to_chunk[i].count); + } + + return 0; +} + +int QTPlayer::readSTSS(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->keyframe_count = _fd->readUint32BE(); + + debug(0, "keyframe_count = %d", st->keyframe_count); + + st->keyframes = new uint32[st->keyframe_count]; + + if (!st->keyframes) + return -1; + + for (uint32 i = 0; i < st->keyframe_count; i++) { + st->keyframes[i] = _fd->readUint32BE(); + debug(6, "keyframes[%d] = %d", i, st->keyframes[i]); + + } + return 0; +} + +int QTPlayer::readSTSZ(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->sample_size = _fd->readUint32BE(); + st->sample_count = _fd->readUint32BE(); + + debug(5, "sample_size = %d sample_count = %d", st->sample_size, st->sample_count); + + if (st->sample_size) + return 0; // there isn't any table following + + st->sample_sizes = new uint32[st->sample_count]; + + if (!st->sample_sizes) + return -1; + + for(uint32 i = 0; i < st->sample_count; i++) { + st->sample_sizes[i] = _fd->readUint32BE(); + debug(6, "sample_sizes[%d] = %d", i, st->sample_sizes[i]); + } + + return 0; +} + +static uint32 ff_gcd(uint32 a, uint32 b) { + if(b) return ff_gcd(b, a%b); + else return a; +} + +int QTPlayer::readSTTS(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + uint32 duration = 0; + uint32 total_sample_count = 0; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->stts_count = _fd->readUint32BE(); + st->stts_data = new MOVstts[st->stts_count]; + + debug(0, "track[%i].stts.entries = %i", _numStreams - 1, st->stts_count); + + st->time_rate = 0; + + for (int32 i = 0; i < st->stts_count; i++) { + int sample_duration; + int sample_count; + + sample_count = _fd->readUint32BE(); + sample_duration = _fd->readUint32BE(); + st->stts_data[i].count = sample_count; + st->stts_data[i].duration = sample_duration; + + st->time_rate = ff_gcd(st->time_rate, sample_duration); + + debug(0, "sample_count=%d, sample_duration=%d", sample_count, sample_duration); + + duration += sample_duration * sample_count; + total_sample_count += sample_count; + } + + st->nb_frames = total_sample_count; + + if (duration) + st->duration = duration; + + return 0; +} + +int QTPlayer::readSTCO(MOVatom atom) { + MOVStreamContext *st = _streams[_numStreams - 1]; + + _fd->readByte(); // version + _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags + + st->chunk_count = _fd->readUint32BE(); + st->chunk_offsets = new uint32[st->chunk_count]; + + if (!st->chunk_offsets) + return -1; + + for (uint32 i = 0; i < st->chunk_count; i++) { + // WORKAROUND/HACK: The offsets in Riven videos (ones inside the Mohawk archives themselves) + // have offsets relative to the archive and not the video. This is quite nasty. We subtract + // the initial offset of the stream to get the correct value inside of the stream. + st->chunk_offsets[i] = _fd->readUint32BE() - _beginOffset; + } + + for (uint32 i = 0; i < _numStreams; i++) { + MOVStreamContext *sc2 = _streams[i]; + + if(sc2 && sc2->chunk_offsets){ + uint32 first = sc2->chunk_offsets[0]; + uint32 last = sc2->chunk_offsets[sc2->chunk_count - 1]; + + if(first >= st->chunk_offsets[st->chunk_count - 1] || last <= st->chunk_offsets[0]) + _ni = 1; + } + } + + return 0; +} + +int QTPlayer::readWAVE(MOVatom atom) { + if (_numStreams < 1) + return 0; + + MOVStreamContext *st = _streams[_numStreams - 1]; + + if (atom.size > (1 << 30)) + return -1; + + if (st->codec_tag == MKID_BE('QDM2')) // Read extradata for QDM2 + st->extradata = _fd->readStream(atom.size - 8); + else if (atom.size > 8) + return readDefault(atom); + else + _fd->skip(atom.size); + + return 0; +} + +void QTPlayer::close() { + stopAudio(); + + delete _videoCodec; _videoCodec = 0; + + for (uint32 i = 0; i < _numStreams; i++) + delete _streams[i]; + + delete _fd; + + if (_scaledSurface) { + _scaledSurface->free(); + delete _scaledSurface; + _scaledSurface = 0; + } + + // The audio stream is deleted automatically + _audStream = NULL; + + Graphics::VideoDecoder::reset(); +} + +Common::SeekableReadStream *QTPlayer::getNextFramePacket() { + if (_videoStreamIndex < 0) + return NULL; + + // First, we have to track down which chunk holds the sample and which sample in the chunk contains the frame we are looking for. + int32 totalSampleCount = 0; + int32 sampleInChunk = 0; + int32 actualChunk = -1; + + for (uint32 i = 0; i < _streams[_videoStreamIndex]->chunk_count; i++) { + int32 sampleToChunkIndex = -1; + + for (uint32 j = 0; j < _streams[_videoStreamIndex]->sample_to_chunk_sz; j++) + if (i >= _streams[_videoStreamIndex]->sample_to_chunk[j].first - 1) + sampleToChunkIndex = j; + + if (sampleToChunkIndex < 0) + error("This chunk (%d) is imaginary", sampleToChunkIndex); + + totalSampleCount += _streams[_videoStreamIndex]->sample_to_chunk[sampleToChunkIndex].count; + + if (totalSampleCount > getCurFrame()) { + actualChunk = i; + sampleInChunk = _streams[_videoStreamIndex]->sample_to_chunk[sampleToChunkIndex].count - totalSampleCount + getCurFrame(); + break; + } + } + + if (actualChunk < 0) { + warning ("Could not find data for frame %d", getCurFrame()); + return NULL; + } + + // Next seek to that frame + _fd->seek(_streams[_videoStreamIndex]->chunk_offsets[actualChunk]); + + // Then, if the chunk holds more than one frame, seek to where the frame we want is located + for (int32 i = getCurFrame() - sampleInChunk; i < getCurFrame(); i++) { + if (_streams[_videoStreamIndex]->sample_size != 0) + _fd->skip(_streams[_videoStreamIndex]->sample_size); + else + _fd->skip(_streams[_videoStreamIndex]->sample_sizes[i]); + } + + // Finally, read in the raw data for the frame + //printf ("Frame Data[%d]: Offset = %d, Size = %d\n", getCurFrame(), _fd->pos(), _streams[_videoStreamIndex]->sample_sizes[getCurFrame()]); + + if (_streams[_videoStreamIndex]->sample_size != 0) + return _fd->readStream(_streams[_videoStreamIndex]->sample_size); + + return _fd->readStream(_streams[_videoStreamIndex]->sample_sizes[getCurFrame()]); +} + +bool QTPlayer::checkAudioCodecSupport(uint32 tag) { + // Check if the codec is a supported codec + if (tag == MKID_BE('twos') || tag == MKID_BE('raw ') || tag == MKID_BE('ima4') || tag == MKID_BE('QDM2')) + return true; + + warning("Audio Codec Not Supported: \'%s\'", tag2str(tag)); + + return false; +} + +Audio::AudioStream *QTPlayer::createAudioStream(Common::SeekableReadStream *stream) { + if (!stream || _audioStreamIndex < 0) + return NULL; + + if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('twos') || _streams[_audioStreamIndex]->codec_tag == MKID_BE('raw ')) { + // Fortunately, most of the audio used in Myst videos is raw... + uint16 flags = 0; + if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('raw ')) + flags |= Audio::FLAG_UNSIGNED; + if (_streams[_audioStreamIndex]->channels == 2) + flags |= Audio::FLAG_STEREO; + if (_streams[_audioStreamIndex]->bits_per_sample == 16) + flags |= Audio::FLAG_16BITS; + uint32 dataSize = stream->size(); + byte *data = (byte *)malloc(dataSize); + stream->read(data, dataSize); + delete stream; + return Audio::makeRawStream(data, dataSize, _streams[_audioStreamIndex]->sample_rate, flags); + } else if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('ima4')) { + // Riven uses this codec (as do some Myst ME videos) + return Audio::makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), Audio::kADPCMApple, _streams[_audioStreamIndex]->sample_rate, _streams[_audioStreamIndex]->channels, 34); + } else if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('QDM2')) { + // Several Myst ME videos use this codec + return new QDM2Stream(stream, _streams[_audioStreamIndex]->extradata); + } + + error("Unsupported audio codec"); + + return NULL; +} + +void QTPlayer::updateAudioBuffer() { + if (!_audStream) + return; + + // Keep two streams in buffer so that when the first ends, it goes right into the next + for (; _audStream->numQueuedStreams() < 2 && _curAudioChunk < _streams[_audioStreamIndex]->chunk_count; _curAudioChunk++) { + Common::MemoryWriteStreamDynamic *wStream = new Common::MemoryWriteStreamDynamic(); + + _fd->seek(_streams[_audioStreamIndex]->chunk_offsets[_curAudioChunk]); + + // First, we have to get the sample count + uint32 sampleCount = 0; + for (uint32 j = 0; j < _streams[_audioStreamIndex]->sample_to_chunk_sz; j++) + if (_curAudioChunk >= (_streams[_audioStreamIndex]->sample_to_chunk[j].first - 1)) + sampleCount = _streams[_audioStreamIndex]->sample_to_chunk[j].count; + assert(sampleCount); + + // Then calculate the right sizes + while (sampleCount > 0) { + uint32 samples = 0, size = 0; + + if (_streams[_audioStreamIndex]->samples_per_frame >= 160) { + samples = _streams[_audioStreamIndex]->samples_per_frame; + size = _streams[_audioStreamIndex]->bytes_per_frame; + } else if (_streams[_audioStreamIndex]->samples_per_frame > 1) { + samples = MIN<uint32>((1024 / _streams[_audioStreamIndex]->samples_per_frame) * _streams[_audioStreamIndex]->samples_per_frame, sampleCount); + size = (samples / _streams[_audioStreamIndex]->samples_per_frame) * _streams[_audioStreamIndex]->bytes_per_frame; + } else { + samples = MIN<uint32>(1024, sampleCount); + size = samples * _streams[_audioStreamIndex]->sample_size; + } + + // Now, we read in the data for this data and output it + byte *data = (byte *)malloc(size); + _fd->read(data, size); + wStream->write(data, size); + free(data); + sampleCount -= samples; + } + + // Now queue the buffer + _audStream->queueAudioStream(createAudioStream(new Common::MemoryReadStream(wStream->getData(), wStream->size(), DisposeAfterUse::YES))); + delete wStream; + } +} + +} // End of namespace Mohawk diff --git a/engines/mohawk/video/qt_player.h b/engines/mohawk/video/qt_player.h new file mode 100644 index 0000000000..6657d3edba --- /dev/null +++ b/engines/mohawk/video/qt_player.h @@ -0,0 +1,282 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// +// Heavily based on ffmpeg code. +// +// Copyright (c) 2001 Fabrice Bellard. +// First version by Francois Revol revol@free.fr +// Seek function by Gael Chardon gael.dev@4now.net +// + +#ifndef MOHAWK_QT_PLAYER_H +#define MOHAWK_QT_PLAYER_H + +#include "common/scummsys.h" +#include "common/queue.h" + +#include "graphics/video/video_decoder.h" +#include "graphics/video/codecs/codec.h" + +#include "sound/audiostream.h" +#include "sound/mixer.h" + +namespace Common { + class File; +} + +namespace Mohawk { + +enum ScaleMode { + kScaleNormal = 1, + kScaleHalf = 2, + kScaleQuarter = 4 +}; + +class QTPlayer : public Graphics::RewindableVideoDecoder { +public: + QTPlayer(); + virtual ~QTPlayer(); + + /** + * Returns the width of the video + * @return the width of the video + */ + uint16 getWidth() const; + + /** + * Returns the height of the video + * @return the height of the video + */ + uint16 getHeight() const; + + /** + * Returns the amount of frames in the video + * @return the amount of frames in the video + */ + uint32 getFrameCount() const; + + /** + * Load a QuickTime video file from a SeekableReadStream + * @param stream the stream to load + */ + bool load(Common::SeekableReadStream &stream); + + /** + * Close a QuickTime encoded video file + */ + void close(); + + /** + * Returns the palette of the video + * @return the palette of the video + */ + byte *getPalette() { _dirtyPalette = false; return _palette; } + bool hasDirtyPalette() const { return _dirtyPalette; } + + /** + * Set the beginning offset of the video so we can modify the offsets in the stco + * atom of videos inside the Mohawk archives + * @param the beginning offset of the video + */ + void setChunkBeginOffset(uint32 offset) { _beginOffset = offset; } + + bool isVideoLoaded() const { return _fd != 0; } + Graphics::Surface *decodeNextFrame(); + bool needsUpdate() const; + bool endOfVideo() const; + uint32 getElapsedTime() const; + uint32 getTimeToNextFrame() const; + Graphics::PixelFormat getPixelFormat() const; + + // RewindableVideoDecoder API + void rewind(); + + // TODO: This audio function need to be removed from the public and/or added to + // the VideoDecoder API directly. I plan on replacing this function with something + // that can figure out how much audio is needed instead of constantly keeping two + // chunks in memory. + void updateAudioBuffer(); + +protected: + // This is the file handle from which data is read from. It can be the actual file handle or a decompressed stream. + Common::SeekableReadStream *_fd; + + struct MOVatom { + uint32 type; + uint32 offset; + uint32 size; + }; + + struct ParseTable { + uint32 type; + int (QTPlayer::*func)(MOVatom atom); + }; + + struct MOVstts { + int count; + int duration; + }; + + struct MOVstsc { + uint32 first; + uint32 count; + uint32 id; + }; + + enum CodecType { + CODEC_TYPE_MOV_OTHER, + CODEC_TYPE_VIDEO, + CODEC_TYPE_AUDIO + }; + + struct MOVStreamContext { + MOVStreamContext() { memset(this, 0, sizeof(MOVStreamContext)); } + ~MOVStreamContext() { + delete[] chunk_offsets; + delete[] stts_data; + delete[] ctts_data; + delete[] sample_to_chunk; + delete[] sample_sizes; + delete[] keyframes; + delete extradata; + } + + int ffindex; /* the ffmpeg stream id */ + int is_ff_stream; /* Is this stream presented to ffmpeg ? i.e. is this an audio or video stream ? */ + uint32 next_chunk; + uint32 chunk_count; + uint32 *chunk_offsets; + int stts_count; + MOVstts *stts_data; + int ctts_count; + MOVstts *ctts_data; + int edit_count; /* number of 'edit' (elst atom) */ + uint32 sample_to_chunk_sz; + MOVstsc *sample_to_chunk; + int32 sample_to_chunk_index; + int sample_to_time_index; + uint32 sample_to_time_sample; + uint32 sample_to_time_time; + int sample_to_ctime_index; + int sample_to_ctime_sample; + uint32 sample_size; + uint32 sample_count; + uint32 *sample_sizes; + uint32 keyframe_count; + uint32 *keyframes; + int32 time_scale; + int time_rate; + uint32 current_sample; + uint32 left_in_chunk; /* how many samples before next chunk */ + + uint16 width; + uint16 height; + int codec_type; + uint32 codec_tag; + char codec_name[32]; + uint16 bits_per_sample; + uint16 color_table_id; + bool palettized; + Common::SeekableReadStream *extradata; + + uint16 stsd_version; + uint16 channels; + uint16 sample_rate; + uint32 samples_per_frame; + uint32 bytes_per_frame; + + uint32 nb_frames; + uint32 duration; + uint32 start_time; + ScaleMode scaleMode; + }; + + const ParseTable *_parseTable; + bool _foundMOOV; + bool _foundMDAT; + uint32 _timeScale; + uint32 _duration; + uint32 _mdatOffset; + uint32 _mdatSize; + uint32 _next_chunk_offset; + MOVStreamContext *_partial; + uint32 _numStreams; + int _ni; + ScaleMode _scaleMode; + MOVStreamContext *_streams[20]; + byte _palette[256 * 3]; + bool _dirtyPalette; + uint32 _beginOffset; + + void initParseTable(); + Audio::AudioStream *createAudioStream(Common::SeekableReadStream *stream); + bool checkAudioCodecSupport(uint32 tag); + Common::SeekableReadStream *getNextFramePacket(); + uint32 getFrameDuration(); + uint32 getCodecTag(); + byte getBitsPerPixel(); + + Audio::QueuingAudioStream *_audStream; + void startAudio(); + void stopAudio(); + int8 _audioStreamIndex; + uint _curAudioChunk; + Audio::SoundHandle _audHandle; + + Graphics::Codec *createCodec(uint32 codecTag, byte bitsPerPixel); + Graphics::Codec *_videoCodec; + uint32 _nextFrameStartTime; + int8 _videoStreamIndex; + + Graphics::Surface *_scaledSurface; + Graphics::Surface *scaleSurface(Graphics::Surface *frame); + ScaleMode getScaleMode() const; + + void pauseVideoIntern(bool pause); + + int readDefault(MOVatom atom); + int readLeaf(MOVatom atom); + int readELST(MOVatom atom); + int readHDLR(MOVatom atom); + int readMDAT(MOVatom atom); + int readMDHD(MOVatom atom); + int readMOOV(MOVatom atom); + int readMVHD(MOVatom atom); + int readTKHD(MOVatom atom); + int readTRAK(MOVatom atom); + int readSTCO(MOVatom atom); + int readSTSC(MOVatom atom); + int readSTSD(MOVatom atom); + int readSTSS(MOVatom atom); + int readSTSZ(MOVatom atom); + int readSTTS(MOVatom atom); + int readCMOV(MOVatom atom); + int readWAVE(MOVatom atom); +}; + +} // End of namespace Mohawk + +#endif diff --git a/engines/mohawk/video/qtrle.cpp b/engines/mohawk/video/qtrle.cpp new file mode 100644 index 0000000000..c06dbefcb3 --- /dev/null +++ b/engines/mohawk/video/qtrle.cpp @@ -0,0 +1,420 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// QuickTime RLE Decoder +// Based off ffmpeg's QuickTime RLE decoder (written by Mike Melanson) + +#include "mohawk/video/qtrle.h" + +#include "common/scummsys.h" +#include "common/stream.h" +#include "common/system.h" +#include "graphics/colormasks.h" +#include "graphics/surface.h" + +namespace Mohawk { + +QTRLEDecoder::QTRLEDecoder(uint16 width, uint16 height, byte bitsPerPixel) : Graphics::Codec() { + _bitsPerPixel = bitsPerPixel; + _pixelFormat = g_system->getScreenFormat(); + + // We need to increase the surface size to a multiple of 4 + uint16 wMod = width % 4; + if(wMod != 0) + width += 4 - wMod; + + debug(2, "QTRLE corrected width: %d", width); + + _surface = new Graphics::Surface(); + _surface->create(width, height, _bitsPerPixel <= 8 ? 1 : _pixelFormat.bytesPerPixel); +} + +#define CHECK_STREAM_PTR(n) \ + if ((stream->pos() + n) > stream->size()) { \ + warning ("Problem: stream out of bounds (%d >= %d)", stream->pos() + n, stream->size()); \ + return; \ + } + +#define CHECK_PIXEL_PTR(n) \ + if ((int32)pixelPtr + n > _surface->w * _surface->h) { \ + warning ("Problem: pixel ptr = %d, pixel limit = %d", pixelPtr + n, _surface->w * _surface->h); \ + return; \ + } \ + +void QTRLEDecoder::decode1(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) { + uint32 pixelPtr = 0; + byte *rgb = (byte *)_surface->pixels; + + while (linesToChange) { + CHECK_STREAM_PTR(2); + byte skip = stream->readByte(); + int8 rleCode = stream->readSByte(); + + if (rleCode == 0) + break; + + if (skip & 0x80) { + linesToChange--; + rowPtr += _surface->w; + pixelPtr = rowPtr + 2 * (skip & 0x7f); + } else + pixelPtr += 2 * skip; + + if (rleCode < 0) { + // decode the run length code + rleCode = -rleCode; + // get the next 2 bytes from the stream, treat them as groups of 8 pixels, and output them rleCode times */ + CHECK_STREAM_PTR(2); + byte pi0 = stream->readByte(); + byte pi1 = stream->readByte(); + CHECK_PIXEL_PTR(rleCode * 2); + + while (rleCode--) { + rgb[pixelPtr++] = pi0; + rgb[pixelPtr++] = pi1; + } + } else { + // copy the same pixel directly to output 2 times + rleCode *= 2; + CHECK_STREAM_PTR(rleCode); + CHECK_PIXEL_PTR(rleCode); + + while (rleCode--) + rgb[pixelPtr++] = stream->readByte(); + } + } +} + +void QTRLEDecoder::decode2_4(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange, byte bpp) { + uint32 pixelPtr = 0; + byte *rgb = (byte *)_surface->pixels; + byte numPixels = (bpp == 4) ? 8 : 16; + + while (linesToChange--) { + CHECK_STREAM_PTR(2); + pixelPtr = rowPtr + (numPixels * (stream->readByte() - 1)); + + for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) { + if (rleCode == 0) { + // there's another skip code in the stream + CHECK_STREAM_PTR(1); + pixelPtr += (numPixels * (stream->readByte() - 1)); + } else if (rleCode < 0) { + // decode the run length code + rleCode = -rleCode; + + // get the next 4 bytes from the stream, treat them as palette indices, and output them rleCode times */ + CHECK_STREAM_PTR(4); + + byte pi[16]; // 16 palette indices + + for (int8 i = numPixels - 1; i >= 0; i--) { + pi[numPixels - 1 - i] = (stream->readByte() >> ((i * bpp) & 0x07)) & ((1 << bpp) - 1); + + // FIXME: Is this right? + //stream_ptr += ((i & ((num_pixels>>2)-1)) == 0); + if ((i & ((numPixels >> 2) - 1)) == 0) + stream->readByte(); + } + + CHECK_PIXEL_PTR(rleCode * numPixels); + + while (rleCode--) + for (byte i = 0; i < numPixels; i++) + rgb[pixelPtr++] = pi[i]; + } else { + // copy the same pixel directly to output 4 times + rleCode *= 4; + CHECK_STREAM_PTR(rleCode); + CHECK_PIXEL_PTR(rleCode * (numPixels >> 2)); + + while (rleCode--) { + byte temp = stream->readByte(); + if (bpp == 4) { + rgb[pixelPtr++] = (temp >> 4) & 0x0f; + rgb[pixelPtr++] = temp & 0x0f; + } else { + rgb[pixelPtr++] = (temp >> 6) & 0x03; + rgb[pixelPtr++] = (temp >> 4) & 0x03; + rgb[pixelPtr++] = (temp >> 2) & 0x03; + rgb[pixelPtr++] = temp & 0x03; + } + } + } + } + + rowPtr += _surface->w; + } +} + +void QTRLEDecoder::decode8(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) { + uint32 pixelPtr = 0; + byte *rgb = (byte *)_surface->pixels; + + while (linesToChange--) { + CHECK_STREAM_PTR(2); + pixelPtr = rowPtr + 4 * (stream->readByte() - 1); + + for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) { + if (rleCode == 0) { + // there's another skip code in the stream + CHECK_STREAM_PTR(1); + pixelPtr += 4 * (stream->readByte() - 1); + } else if (rleCode < 0) { + // decode the run length code + rleCode = -rleCode; + + // get the next 4 bytes from the stream, treat them as palette indices, and output them rleCode times + CHECK_STREAM_PTR(4); + + byte pi[4]; // 4 palette indexes + + for (byte i = 0; i < 4; i++) + pi[i] = stream->readByte(); + + CHECK_PIXEL_PTR(rleCode * 4); + + while (rleCode--) + for (byte i = 0; i < 4; i++) + rgb[pixelPtr++] = pi[i]; + } else { + // copy the same pixel directly to output 4 times + rleCode *= 4; + CHECK_STREAM_PTR(rleCode); + CHECK_PIXEL_PTR(rleCode); + + while (rleCode--) + rgb[pixelPtr++] = stream->readByte(); + } + } + + rowPtr += _surface->w; + } +} + +void QTRLEDecoder::decode16(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) { + uint32 pixelPtr = 0; + OverlayColor *rgb = (OverlayColor *)_surface->pixels; + + while (linesToChange--) { + CHECK_STREAM_PTR(2); + pixelPtr = rowPtr + stream->readByte() - 1; + + for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) { + if (rleCode == 0) { + // there's another skip code in the stream + CHECK_STREAM_PTR(1); + pixelPtr += stream->readByte() - 1; + } else if (rleCode < 0) { + // decode the run length code + rleCode = -rleCode; + CHECK_STREAM_PTR(2); + + uint16 rgb16 = stream->readUint16BE(); + + CHECK_PIXEL_PTR(rleCode); + + while (rleCode--) { + // Convert from RGB555 to the format specified by the Overlay + byte r = 0, g = 0, b = 0; + Graphics::colorToRGB<Graphics::ColorMasks<555> >(rgb16, r, g, b); + rgb[pixelPtr++] = _pixelFormat.RGBToColor(r, g, b); + } + } else { + CHECK_STREAM_PTR(rleCode * 2); + CHECK_PIXEL_PTR(rleCode); + + // copy pixels directly to output + while (rleCode--) { + uint16 rgb16 = stream->readUint16BE(); + + // Convert from RGB555 to the format specified by the Overlay + byte r = 0, g = 0, b = 0; + Graphics::colorToRGB<Graphics::ColorMasks<555> >(rgb16, r, g, b); + rgb[pixelPtr++] = _pixelFormat.RGBToColor(r, g, b); + } + } + } + + rowPtr += _surface->w; + } +} + +void QTRLEDecoder::decode24(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) { + uint32 pixelPtr = 0; + OverlayColor *rgb = (OverlayColor *)_surface->pixels; + + while (linesToChange--) { + CHECK_STREAM_PTR(2); + pixelPtr = rowPtr + stream->readByte() - 1; + + for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) { + if (rleCode == 0) { + // there's another skip code in the stream + CHECK_STREAM_PTR(1); + pixelPtr += stream->readByte() - 1; + } else if (rleCode < 0) { + // decode the run length code + rleCode = -rleCode; + + CHECK_STREAM_PTR(3); + + byte r = stream->readByte(); + byte g = stream->readByte(); + byte b = stream->readByte(); + + CHECK_PIXEL_PTR(rleCode); + + while (rleCode--) + rgb[pixelPtr++] = _pixelFormat.RGBToColor(r, g, b); + } else { + CHECK_STREAM_PTR(rleCode * 3); + CHECK_PIXEL_PTR(rleCode); + + // copy pixels directly to output + while (rleCode--) { + byte r = stream->readByte(); + byte g = stream->readByte(); + byte b = stream->readByte(); + rgb[pixelPtr++] = _pixelFormat.RGBToColor(r, g, b); + } + } + } + + rowPtr += _surface->w; + } +} + +void QTRLEDecoder::decode32(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) { + uint32 pixelPtr = 0; + OverlayColor *rgb = (OverlayColor *)_surface->pixels; + + while (linesToChange--) { + CHECK_STREAM_PTR(2); + pixelPtr = rowPtr + stream->readByte() - 1; + + for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) { + if (rleCode == 0) { + // there's another skip code in the stream + CHECK_STREAM_PTR(1); + pixelPtr += stream->readByte() - 1; + } else if (rleCode < 0) { + // decode the run length code + rleCode = -rleCode; + + CHECK_STREAM_PTR(4); + + byte a = stream->readByte(); + byte r = stream->readByte(); + byte g = stream->readByte(); + byte b = stream->readByte(); + + CHECK_PIXEL_PTR(rleCode); + + while (rleCode--) + rgb[pixelPtr++] = _pixelFormat.ARGBToColor(a, r, g, b); + } else { + CHECK_STREAM_PTR(rleCode * 4); + CHECK_PIXEL_PTR(rleCode); + + // copy pixels directly to output + while (rleCode--) { + byte a = stream->readByte(); + byte r = stream->readByte(); + byte g = stream->readByte(); + byte b = stream->readByte(); + rgb[pixelPtr++] = _pixelFormat.ARGBToColor(a, r, g, b); + } + } + } + + rowPtr += _surface->w; + } +} + +Graphics::Surface *QTRLEDecoder::decodeImage(Common::SeekableReadStream *stream) { + uint16 start_line = 0; + uint16 height = _surface->h; + + // check if this frame is even supposed to change + if (stream->size() < 8) + return _surface; + + // start after the chunk size + stream->readUint32BE(); + + // fetch the header + uint16 header = stream->readUint16BE(); + + // if a header is present, fetch additional decoding parameters + if (header & 8) { + if(stream->size() < 14) + return _surface; + start_line = stream->readUint16BE(); + stream->readUint16BE(); // Unknown + height = stream->readUint16BE(); + stream->readUint16BE(); // Unknown + } + + uint32 row_ptr = _surface->w * start_line; + + switch (_bitsPerPixel) { + case 1: + case 33: + decode1(stream, row_ptr, height); + break; + case 2: + case 34: + decode2_4(stream, row_ptr, height, 2); + break; + case 4: + case 36: + decode2_4(stream, row_ptr, height, 4); + break; + case 8: + case 40: + decode8(stream, row_ptr, height); + break; + case 16: + decode16(stream, row_ptr, height); + break; + case 24: + decode24(stream, row_ptr, height); + break; + case 32: + decode32(stream, row_ptr, height); + break; + default: + error ("Unsupported bits per pixel %d", _bitsPerPixel); + } + + return _surface; +} + +QTRLEDecoder::~QTRLEDecoder() { + _surface->free(); +} + +} // End of namespace Mohawk diff --git a/engines/mohawk/video/qtrle.h b/engines/mohawk/video/qtrle.h new file mode 100644 index 0000000000..2832bd6b24 --- /dev/null +++ b/engines/mohawk/video/qtrle.h @@ -0,0 +1,58 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef MOHAWK_QTRLE_H +#define MOHAWK_QTRLE_H + +#include "graphics/pixelformat.h" +#include "graphics/video/codecs/codec.h" + +namespace Mohawk { + +class QTRLEDecoder : public Graphics::Codec { +public: + QTRLEDecoder(uint16 width, uint16 height, byte bitsPerPixel); + ~QTRLEDecoder(); + + Graphics::Surface *decodeImage(Common::SeekableReadStream *stream); + Graphics::PixelFormat getPixelFormat() const { return _pixelFormat; } + +private: + byte _bitsPerPixel; + + Graphics::Surface *_surface; + Graphics::PixelFormat _pixelFormat; + + void decode1(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange); + void decode2_4(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange, byte bpp); + void decode8(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange); + void decode16(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange); + void decode24(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange); + void decode32(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange); +}; + +} // End of namespace Mohawk + +#endif diff --git a/engines/mohawk/video/rpza.cpp b/engines/mohawk/video/rpza.cpp new file mode 100644 index 0000000000..f48c055ae2 --- /dev/null +++ b/engines/mohawk/video/rpza.cpp @@ -0,0 +1,208 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + + // Based off ffmpeg's RPZA decoder + +#include "mohawk/video/rpza.h" + +#include "common/system.h" +#include "graphics/colormasks.h" + +namespace Mohawk { + +RPZADecoder::RPZADecoder(uint16 width, uint16 height) : Graphics::Codec() { + _pixelFormat = g_system->getScreenFormat(); + + // We need to increase the surface size to a multiple of 4 + uint16 wMod = width % 4; + if(wMod != 0) + width += 4 - wMod; + + debug(2, "RPZA corrected width: %d", width); + + _surface = new Graphics::Surface(); + _surface->create(width, height, _pixelFormat.bytesPerPixel); +} + +#define ADVANCE_BLOCK() \ + pixelPtr += 4; \ + if (pixelPtr >= _surface->w) { \ + pixelPtr = 0; \ + rowPtr += _surface->w * 4; \ + } \ + totalBlocks--; \ + if (totalBlocks < 0) \ + error("block counter just went negative (this should not happen)") \ + +// Convert from RGB555 to the format specified by the screen +#define PUT_PIXEL(color) \ + if ((int32)blockPtr < _surface->w * _surface->h) { \ + byte r = 0, g = 0, b = 0; \ + Graphics::colorToRGB<Graphics::ColorMasks<555> >(color, r, g, b); \ + if (_pixelFormat.bytesPerPixel == 2) \ + *((uint16 *)_surface->pixels + blockPtr) = _pixelFormat.RGBToColor(r, g, b); \ + else \ + *((uint32 *)_surface->pixels + blockPtr) = _pixelFormat.RGBToColor(r, g, b); \ + } \ + blockPtr++ + +Graphics::Surface *RPZADecoder::decodeImage(Common::SeekableReadStream *stream) { + uint16 colorA = 0, colorB = 0; + uint16 color4[4]; + + uint32 rowPtr = 0; + uint32 pixelPtr = 0; + uint32 blockPtr = 0; + uint32 rowInc = _surface->w - 4; + uint16 ta; + uint16 tb; + + // First byte is always 0xe1. Warn if it's different + byte firstByte = stream->readByte(); + if (firstByte != 0xe1) + warning("First RPZA chunk byte is 0x%02x instead of 0xe1", firstByte); + + // Get chunk size, ingnoring first byte + uint32 chunkSize = stream->readUint16BE() << 8; + chunkSize += stream->readByte(); + + // If length mismatch use size from MOV file and try to decode anyway + if (chunkSize != (uint32)stream->size()) { + warning("MOV chunk size != encoded chunk size; using MOV chunk size"); + chunkSize = stream->size(); + } + + // Number of 4x4 blocks in frame + int32 totalBlocks = ((_surface->w + 3) / 4) * ((_surface->h + 3) / 4); + + // Process chunk data + while ((uint32)stream->pos() < chunkSize) { + byte opcode = stream->readByte(); // Get opcode + byte numBlocks = (opcode & 0x1f) + 1; // Extract block counter from opcode + + // If opcode MSbit is 0, we need more data to decide what to do + if ((opcode & 0x80) == 0) { + colorA = (opcode << 8) | stream->readByte(); + opcode = 0; + if (stream->readByte() & 0x80) { + // Must behave as opcode 110xxxxx, using colorA computed + // above. Use fake opcode 0x20 to enter switch block at + // the right place + opcode = 0x20; + numBlocks = 1; + } + stream->seek(-1, SEEK_CUR); + } + + switch (opcode & 0xe0) { + case 0x80: // Skip blocks + while (numBlocks--) { + ADVANCE_BLOCK(); + } + break; + case 0xa0: // Fill blocks with one color + colorA = stream->readUint16BE(); + while (numBlocks--) { + blockPtr = rowPtr + pixelPtr; + for (byte pixel_y = 0; pixel_y < 4; pixel_y++) { + for (byte pixel_x = 0; pixel_x < 4; pixel_x++) { + PUT_PIXEL(colorA); + } + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // Fill blocks with 4 colors + case 0xc0: + colorA = stream->readUint16BE(); + case 0x20: + colorB = stream->readUint16BE(); + + // Sort out the colors + color4[0] = colorB; + color4[1] = 0; + color4[2] = 0; + color4[3] = colorA; + + // Red components + ta = (colorA >> 10) & 0x1F; + tb = (colorB >> 10) & 0x1F; + color4[1] |= ((11 * ta + 21 * tb) >> 5) << 10; + color4[2] |= ((21 * ta + 11 * tb) >> 5) << 10; + + // Green components + ta = (colorA >> 5) & 0x1F; + tb = (colorB >> 5) & 0x1F; + color4[1] |= ((11 * ta + 21 * tb) >> 5) << 5; + color4[2] |= ((21 * ta + 11 * tb) >> 5) << 5; + + // Blue components + ta = colorA & 0x1F; + tb = colorB & 0x1F; + color4[1] |= ((11 * ta + 21 * tb) >> 5); + color4[2] |= ((21 * ta + 11 * tb) >> 5); + + while (numBlocks--) { + blockPtr = rowPtr + pixelPtr; + for (byte pixel_y = 0; pixel_y < 4; pixel_y++) { + byte index = stream->readByte(); + for (byte pixel_x = 0; pixel_x < 4; pixel_x++){ + byte idx = (index >> (2 * (3 - pixel_x))) & 0x03; + PUT_PIXEL(color4[idx]); + } + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // Fill block with 16 colors + case 0x00: + blockPtr = rowPtr + pixelPtr; + for (byte pixel_y = 0; pixel_y < 4; pixel_y++) { + for (byte pixel_x = 0; pixel_x < 4; pixel_x++){ + // We already have color of upper left pixel + if (pixel_y != 0 || pixel_x != 0) + colorA = stream->readUint16BE(); + + PUT_PIXEL(colorA); + } + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + break; + + // Unknown opcode + default: + error("Unknown opcode %02x in rpza chunk", opcode); + } + } + + return _surface; +} + +} // End of namespace Mohawk diff --git a/engines/mohawk/video/rpza.h b/engines/mohawk/video/rpza.h new file mode 100644 index 0000000000..c6d0ada6f5 --- /dev/null +++ b/engines/mohawk/video/rpza.h @@ -0,0 +1,49 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef MOHAWK_RPZA_H +#define MOHAWK_RPZA_H + +#include "graphics/pixelformat.h" +#include "graphics/video/codecs/codec.h" + +namespace Mohawk { + +class RPZADecoder : public Graphics::Codec { +public: + RPZADecoder(uint16 width, uint16 height); + ~RPZADecoder() { delete _surface; } + + Graphics::Surface *decodeImage(Common::SeekableReadStream *stream); + Graphics::PixelFormat getPixelFormat() const { return _pixelFormat; } + +private: + Graphics::Surface *_surface; + Graphics::PixelFormat _pixelFormat; +}; + +} // End of namespace Mohawk + +#endif diff --git a/engines/mohawk/video/smc.cpp b/engines/mohawk/video/smc.cpp new file mode 100644 index 0000000000..4a0d16dfcc --- /dev/null +++ b/engines/mohawk/video/smc.cpp @@ -0,0 +1,385 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// Based off ffmpeg's SMC decoder + +#include "mohawk/video/smc.h" + +namespace Mohawk { + +#define GET_BLOCK_COUNT() \ + (opcode & 0x10) ? (1 + stream->readByte()) : 1 + (opcode & 0x0F); + +#define ADVANCE_BLOCK() \ +{ \ + pixelPtr += 4; \ + if (pixelPtr >= _surface->w) { \ + pixelPtr = 0; \ + rowPtr += _surface->w * 4; \ + } \ + totalBlocks--; \ + if (totalBlocks < 0) { \ + warning("block counter just went negative (this should not happen)"); \ + return _surface; \ + } \ +} + +SMCDecoder::SMCDecoder(uint16 width, uint16 height) { + _surface = new Graphics::Surface(); + _surface->create(width, height, 1); +} + +Graphics::Surface *SMCDecoder::decodeImage(Common::SeekableReadStream *stream) { + byte *pixels = (byte *)_surface->pixels; + + uint32 numBlocks = 0; + uint32 colorFlags = 0; + uint32 colorFlagsA = 0; + uint32 colorFlagsB = 0; + + const uint16 rowInc = _surface->w - 4; + int32 rowPtr = 0; + int32 pixelPtr = 0; + uint32 blockPtr = 0; + uint32 prevBlockPtr = 0; + uint32 prevBlockPtr1 = 0, prevBlockPtr2 = 0; + byte prevBlockFlag = false; + byte pixel = 0; + + uint32 colorPairIndex = 0; + uint32 colorQuadIndex = 0; + uint32 colorOctetIndex = 0; + uint32 colorTableIndex = 0; // indices to color pair, quad, or octet tables + + int32 chunkSize = stream->readUint32BE() & 0x00FFFFFF; + if (chunkSize != stream->size()) + warning("MOV chunk size != SMC chunk size (%d != %d); ignoring SMC chunk size", chunkSize, stream->size()); + + int32 totalBlocks = ((_surface->w + 3) / 4) * ((_surface->h + 3) / 4); + + // traverse through the blocks + while (totalBlocks != 0) { + // sanity checks + + // make sure stream ptr hasn't gone out of bounds + if (stream->pos() > stream->size()) { + warning("SMC decoder just went out of bounds (stream ptr = %d, chunk size = %d)", stream->pos(), stream->size()); + return _surface; + } + + // make sure the row pointer hasn't gone wild + if (rowPtr >= _surface->w * _surface->h) { + warning("SMC decoder just went out of bounds (row ptr = %d, size = %d)", rowPtr, _surface->w * _surface->h); + return _surface; + } + + byte opcode = stream->readByte(); + + switch (opcode & 0xF0) { + // skip n blocks + case 0x00: + case 0x10: + numBlocks = GET_BLOCK_COUNT(); + while (numBlocks--) { + ADVANCE_BLOCK(); + } + break; + + // repeat last block n times + case 0x20: + case 0x30: + numBlocks = GET_BLOCK_COUNT(); + + // sanity check + if (rowPtr == 0 && pixelPtr == 0) { + warning("encountered repeat block opcode (%02X) but no blocks rendered yet", opcode & 0xF0); + break; + } + + // figure out where the previous block started + if (pixelPtr == 0) + prevBlockPtr1 = (rowPtr - _surface->w * 4) + _surface->w - 4; + else + prevBlockPtr1 = rowPtr + pixelPtr - 4; + + while (numBlocks--) { + blockPtr = rowPtr + pixelPtr; + prevBlockPtr = prevBlockPtr1; + for (byte y = 0; y < 4; y++) { + for (byte x = 0; x < 4; x++) + pixels[blockPtr++] = pixels[prevBlockPtr++]; + blockPtr += rowInc; + prevBlockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // repeat previous pair of blocks n times + case 0x40: + case 0x50: + numBlocks = GET_BLOCK_COUNT(); + numBlocks *= 2; + + // sanity check + if (rowPtr == 0 && pixelPtr < 2 * 4) { + warning("encountered repeat block opcode (%02X) but not enough blocks rendered yet", opcode & 0xF0); + break; + } + + // figure out where the previous 2 blocks started + if (pixelPtr == 0) + prevBlockPtr1 = (rowPtr - _surface->w * 4) + _surface->w - 4 * 2; + else if (pixelPtr == 4) + prevBlockPtr1 = (rowPtr - _surface->w * 4) + rowInc; + else + prevBlockPtr1 = rowPtr + pixelPtr - 4 * 2; + + if (pixelPtr == 0) + prevBlockPtr2 = (rowPtr - _surface->w * 4) + rowInc; + else + prevBlockPtr2 = rowPtr + pixelPtr - 4; + + prevBlockFlag = 0; + while (numBlocks--) { + blockPtr = rowPtr + pixelPtr; + + if (prevBlockFlag) + prevBlockPtr = prevBlockPtr2; + else + prevBlockPtr = prevBlockPtr1; + + prevBlockFlag = !prevBlockFlag; + + for (byte y = 0; y < 4; y++) { + for (byte x = 0; x < 4; x++) + pixels[blockPtr++] = pixels[prevBlockPtr++]; + + blockPtr += rowInc; + prevBlockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // 1-color block encoding + case 0x60: + case 0x70: + numBlocks = GET_BLOCK_COUNT(); + pixel = stream->readByte(); + + while (numBlocks--) { + blockPtr = rowPtr + pixelPtr; + for (byte y = 0; y < 4; y++) { + for (byte x = 0; x < 4; x++) + pixels[blockPtr++] = pixel; + + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // 2-color block encoding + case 0x80: + case 0x90: + numBlocks = (opcode & 0x0F) + 1; + + // figure out which color pair to use to paint the 2-color block + if ((opcode & 0xF0) == 0x80) { + // fetch the next 2 colors from bytestream and store in next + // available entry in the color pair table + for (byte i = 0; i < CPAIR; i++) { + pixel = stream->readByte(); + colorTableIndex = CPAIR * colorPairIndex + i; + _colorPairs[colorTableIndex] = pixel; + } + + // this is the base index to use for this block + colorTableIndex = CPAIR * colorPairIndex; + colorPairIndex++; + + // wraparound + if (colorPairIndex == COLORS_PER_TABLE) + colorPairIndex = 0; + } else + colorTableIndex = CPAIR * stream->readByte(); + + while (numBlocks--) { + colorFlags = stream->readUint16BE(); + uint16 flagMask = 0x8000; + blockPtr = rowPtr + pixelPtr; + for (byte y = 0; y < 4; y++) { + for (byte x = 0; x < 4; x++) { + if (colorFlags & flagMask) + pixel = colorTableIndex + 1; + else + pixel = colorTableIndex; + + flagMask >>= 1; + pixels[blockPtr++] = _colorPairs[pixel]; + } + + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // 4-color block encoding + case 0xA0: + case 0xB0: + numBlocks = (opcode & 0x0F) + 1; + + // figure out which color quad to use to paint the 4-color block + if ((opcode & 0xF0) == 0xA0) { + // fetch the next 4 colors from bytestream and store in next + // available entry in the color quad table + for (byte i = 0; i < CQUAD; i++) { + pixel = stream->readByte(); + colorTableIndex = CQUAD * colorQuadIndex + i; + _colorQuads[colorTableIndex] = pixel; + } + + // this is the base index to use for this block + colorTableIndex = CQUAD * colorQuadIndex; + colorQuadIndex++; + + // wraparound + if (colorQuadIndex == COLORS_PER_TABLE) + colorQuadIndex = 0; + } else + colorTableIndex = CQUAD * stream->readByte(); + + while (numBlocks--) { + colorFlags = stream->readUint32BE(); + + // flag mask actually acts as a bit shift count here + byte flagMask = 30; + blockPtr = rowPtr + pixelPtr; + + for (byte y = 0; y < 4; y++) { + for (byte x = 0; x < 4; x++) { + pixel = colorTableIndex + ((colorFlags >> flagMask) & 0x03); + flagMask -= 2; + pixels[blockPtr++] = _colorQuads[pixel]; + } + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // 8-color block encoding + case 0xC0: + case 0xD0: + numBlocks = (opcode & 0x0F) + 1; + + // figure out which color octet to use to paint the 8-color block + if ((opcode & 0xF0) == 0xC0) { + // fetch the next 8 colors from bytestream and store in next + // available entry in the color octet table + for (byte i = 0; i < COCTET; i++) { + pixel = stream->readByte(); + colorTableIndex = COCTET * colorOctetIndex + i; + _colorOctets[colorTableIndex] = pixel; + } + + // this is the base index to use for this block + colorTableIndex = COCTET * colorOctetIndex; + colorOctetIndex++; + + // wraparound + if (colorOctetIndex == COLORS_PER_TABLE) + colorOctetIndex = 0; + } else + colorTableIndex = COCTET * stream->readByte(); + + while (numBlocks--) { + /* + For this input of 6 hex bytes: + 01 23 45 67 89 AB + Mangle it to this output: + flags_a = xx012456, flags_b = xx89A37B + */ + + // build the color flags + byte flagData[6]; + stream->read(flagData, 6); + + colorFlagsA = ((READ_BE_UINT16(flagData) & 0xFFF0) << 8) | (READ_BE_UINT16(flagData + 2) >> 4); + colorFlagsB = ((READ_BE_UINT16(flagData + 4) & 0xFFF0) << 8) | ((flagData[1] & 0xF) << 8) | + ((flagData[3] & 0xF) << 4) | (flagData[5] & 0xf); + + colorFlags = colorFlagsA; + + // flag mask actually acts as a bit shift count here + byte flagMask = 21; + blockPtr = rowPtr + pixelPtr; + for (byte y = 0; y < 4; y++) { + // reload flags at third row (iteration y == 2) + if (y == 2) { + colorFlags = colorFlagsB; + flagMask = 21; + } + + for (byte x = 0; x < 4; x++) { + pixel = colorTableIndex + ((colorFlags >> flagMask) & 0x07); + flagMask -= 3; + pixels[blockPtr++] = _colorOctets[pixel]; + } + + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + // 16-color block encoding (every pixel is a different color) + case 0xE0: + numBlocks = (opcode & 0x0F) + 1; + + while (numBlocks--) { + blockPtr = rowPtr + pixelPtr; + for (byte y = 0; y < 4; y++) { + for (byte x = 0; x < 4; x++) + pixels[blockPtr++] = stream->readByte(); + + blockPtr += rowInc; + } + ADVANCE_BLOCK(); + } + break; + + case 0xF0: + warning("0xF0 opcode seen in SMC chunk (contact the developers)"); + break; + } + } + + return _surface; +} + +} // End of namespace Mohawk diff --git a/engines/mohawk/video/smc.h b/engines/mohawk/video/smc.h new file mode 100644 index 0000000000..c52226100e --- /dev/null +++ b/engines/mohawk/video/smc.h @@ -0,0 +1,59 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef MOHAWK_VIDEO_SMC_H +#define MOHAWK_VIDEO_SMC_H + +#include "graphics/video/codecs/codec.h" + +namespace Mohawk { + +enum { + CPAIR = 2, + CQUAD = 4, + COCTET = 8, + COLORS_PER_TABLE = 256 +}; + +class SMCDecoder : public Graphics::Codec { +public: + SMCDecoder(uint16 width, uint16 height); + ~SMCDecoder() { delete _surface; } + + Graphics::Surface *decodeImage(Common::SeekableReadStream *stream); + Graphics::PixelFormat getPixelFormat() const { return Graphics::PixelFormat::createFormatCLUT8(); } + +private: + Graphics::Surface *_surface; + + // SMC color tables + byte _colorPairs[COLORS_PER_TABLE * CPAIR]; + byte _colorQuads[COLORS_PER_TABLE * CQUAD]; + byte _colorOctets[COLORS_PER_TABLE * COCTET]; +}; + +} // End of namespace Mohawk + +#endif diff --git a/engines/mohawk/video/video.cpp b/engines/mohawk/video/video.cpp new file mode 100644 index 0000000000..86ecd4dedf --- /dev/null +++ b/engines/mohawk/video/video.cpp @@ -0,0 +1,377 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "mohawk/resource.h" +#include "mohawk/video/video.h" +#include "mohawk/video/qt_player.h" + +#include "common/events.h" + +namespace Mohawk { + +VideoManager::VideoManager(MohawkEngine* vm) : _vm(vm) { +} + +VideoManager::~VideoManager() { + _mlstRecords.clear(); + stopVideos(); +} + +void VideoManager::pauseVideos() { + for (uint16 i = 0; i < _videoStreams.size(); i++) + _videoStreams[i]->pauseVideo(true); +} + +void VideoManager::resumeVideos() { + for (uint16 i = 0; i < _videoStreams.size(); i++) + _videoStreams[i]->pauseVideo(false); +} + +void VideoManager::stopVideos() { + for (uint16 i = 0; i < _videoStreams.size(); i++) + delete _videoStreams[i].video; + _videoStreams.clear(); +} + +void VideoManager::playMovie(Common::String filename, uint16 x, uint16 y, bool clearScreen) { + VideoHandle videoHandle = createVideoHandle(filename, x, y, false); + if (videoHandle == NULL_VID_HANDLE) + return; + + // Clear screen if requested + if (clearScreen) { + _vm->_system->fillScreen(_vm->_system->getScreenFormat().RGBToColor(0, 0, 0)); + _vm->_system->updateScreen(); + } + + waitUntilMovieEnds(videoHandle); +} + +void VideoManager::playMovieCentered(Common::String filename, bool clearScreen) { + VideoHandle videoHandle = createVideoHandle(filename, 0, 0, false); + if (videoHandle == NULL_VID_HANDLE) + return; + + // Clear screen if requested + if (clearScreen) { + _vm->_system->fillScreen(_vm->_system->getScreenFormat().RGBToColor(0, 0, 0)); + _vm->_system->updateScreen(); + } + + _videoStreams[videoHandle].x = (_vm->_system->getWidth() - _videoStreams[videoHandle]->getWidth()) / 2; + _videoStreams[videoHandle].y = (_vm->_system->getHeight() - _videoStreams[videoHandle]->getHeight()) / 2; + + waitUntilMovieEnds(videoHandle); +} + +void VideoManager::waitUntilMovieEnds(VideoHandle videoHandle) { + bool continuePlaying = true; + + while (!_videoStreams[videoHandle]->endOfVideo() && !_vm->shouldQuit() && continuePlaying) { + if (updateBackgroundMovies()) + _vm->_system->updateScreen(); + + Common::Event event; + while (_vm->_system->getEventManager()->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_RTL: + case Common::EVENT_QUIT: + continuePlaying = false; + break; + case Common::EVENT_KEYDOWN: + switch (event.kbd.keycode) { + case Common::KEYCODE_SPACE: + _vm->pauseGame(); + break; + case Common::KEYCODE_ESCAPE: + continuePlaying = false; + break; + default: + break; + } + default: + break; + } + } + + // Cut down on CPU usage + _vm->_system->delayMillis(10); + } + + _videoStreams[videoHandle]->close(); + _videoStreams.clear(); +} + +void VideoManager::playBackgroundMovie(Common::String filename, int16 x, int16 y, bool loop) { + VideoHandle videoHandle = createVideoHandle(filename, x, y, loop); + if (videoHandle == NULL_VID_HANDLE) + return; + + // Center x if requested + if (x < 0) + _videoStreams[videoHandle].x = (_vm->_system->getWidth() - _videoStreams[videoHandle]->getWidth()) / 2; + + // Center y if requested + if (y < 0) + _videoStreams[videoHandle].y = (_vm->_system->getHeight() - _videoStreams[videoHandle]->getHeight()) / 2; +} + +bool VideoManager::updateBackgroundMovies() { + bool updateScreen = false; + + for (uint32 i = 0; i < _videoStreams.size() && !_vm->shouldQuit(); i++) { + // Skip deleted videos + if (!_videoStreams[i].video) + continue; + + // Remove any videos that are over + if (_videoStreams[i]->endOfVideo()) { + if (_videoStreams[i].loop) { + _videoStreams[i]->rewind(); + } else { + delete _videoStreams[i].video; + memset(&_videoStreams[i], 0, sizeof(VideoEntry)); + _videoStreams[i].video = NULL; + continue; + } + } + + // Check if we need to draw a frame + if (_videoStreams[i]->needsUpdate()) { + Graphics::Surface *frame = _videoStreams[i]->decodeNextFrame(); + bool deleteFrame = false; + + if (frame && _videoStreams[i].enabled) { + // Convert from 8bpp to the current screen format if necessary + if (frame->bytesPerPixel == 1) { + Graphics::Surface *newFrame = new Graphics::Surface(); + Graphics::PixelFormat pixelFormat = _vm->_system->getScreenFormat(); + byte *palette = _videoStreams[i]->getPalette(); + assert(palette); + + newFrame->create(frame->w, frame->h, pixelFormat.bytesPerPixel); + + for (uint16 j = 0; j < frame->h; j++) { + for (uint16 k = 0; k < frame->w; k++) { + byte palIndex = *((byte *)frame->getBasePtr(k, j)); + byte r = palette[palIndex * 3]; + byte g = palette[palIndex * 3 + 1]; + byte b = palette[palIndex * 3 + 2]; + if (pixelFormat.bytesPerPixel == 2) + *((uint16 *)newFrame->getBasePtr(k, j)) = pixelFormat.RGBToColor(r, g, b); + else + *((uint32 *)newFrame->getBasePtr(k, j)) = pixelFormat.RGBToColor(r, g, b); + } + } + + frame = newFrame; + deleteFrame = true; + } + + // Clip the width/height to make sure we stay on the screen (Myst does this a few times) + uint16 width = MIN<int32>(_videoStreams[i]->getWidth(), _vm->_system->getWidth() - _videoStreams[i].x); + uint16 height = MIN<int32>(_videoStreams[i]->getHeight(), _vm->_system->getHeight() - _videoStreams[i].y); + _vm->_system->copyRectToScreen((byte*)frame->pixels, frame->pitch, _videoStreams[i].x, _videoStreams[i].y, width, height); + + // We've drawn something to the screen, make sure we update it + updateScreen = true; + + // Delete the frame if we're using the buffer from the 8bpp conversion + if (deleteFrame) { + frame->free(); + delete frame; + } + } + } + + // Update the audio buffer too + _videoStreams[i]->updateAudioBuffer(); + } + + // Return true if we need to update the screen + return updateScreen; +} + +void VideoManager::activateMLST(uint16 mlstId, uint16 card) { + Common::SeekableReadStream *mlstStream = _vm->getRawData(ID_MLST, card); + uint16 recordCount = mlstStream->readUint16BE(); + + for (uint16 i = 0; i < recordCount; i++) { + MLSTRecord mlstRecord; + mlstRecord.index = mlstStream->readUint16BE(); + mlstRecord.movieID = mlstStream->readUint16BE(); + mlstRecord.code = mlstStream->readUint16BE(); + mlstRecord.left = mlstStream->readUint16BE(); + mlstRecord.top = mlstStream->readUint16BE(); + + for (byte j = 0; j < 2; j++) + if (mlstStream->readUint16BE() != 0) + warning("u0[%d] in MLST non-zero", j); + + if (mlstStream->readUint16BE() != 0xFFFF) + warning("u0[2] in MLST not 0xFFFF"); + + mlstRecord.loop = mlstStream->readUint16BE(); + mlstRecord.volume = mlstStream->readUint16BE(); + mlstRecord.u1 = mlstStream->readUint16BE(); + + if (mlstRecord.u1 != 1) + warning("mlstRecord.u1 not 1"); + + if (mlstRecord.index == mlstId) { + _mlstRecords.push_back(mlstRecord); + break; + } + } + + delete mlstStream; +} + +void VideoManager::playMovie(uint16 id) { + for (uint16 i = 0; i < _mlstRecords.size(); i++) + if (_mlstRecords[i].code == id) { + debug(1, "Play tMOV %d (non-blocking) at (%d, %d) %s", _mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top, _mlstRecords[i].loop != 0 ? "looping" : "non-looping"); + createVideoHandle(_mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top, _mlstRecords[i].loop != 0); + return; + } +} + +void VideoManager::playMovieBlocking(uint16 id) { + for (uint16 i = 0; i < _mlstRecords.size(); i++) + if (_mlstRecords[i].code == id) { + debug(1, "Play tMOV %d (blocking) at (%d, %d)", _mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top); + VideoHandle videoHandle = createVideoHandle(_mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top, false); + waitUntilMovieEnds(videoHandle); + return; + } +} + +void VideoManager::stopMovie(uint16 id) { + debug(2, "Stopping movie %d", id); + for (uint16 i = 0; i < _mlstRecords.size(); i++) + if (_mlstRecords[i].code == id) + for (uint16 j = 0; j < _videoStreams.size(); j++) + if (_mlstRecords[i].movieID == _videoStreams[j].id) { + delete _videoStreams[i].video; + memset(&_videoStreams[i].video, 0, sizeof(VideoEntry)); + return; + } +} + +void VideoManager::enableMovie(uint16 id) { + debug(2, "Enabling movie %d", id); + for (uint16 i = 0; i < _mlstRecords.size(); i++) + if (_mlstRecords[i].code == id) + for (uint16 j = 0; j < _videoStreams.size(); j++) + if (_mlstRecords[i].movieID == _videoStreams[j].id) { + _videoStreams[j].enabled = true; + return; + } +} + +void VideoManager::disableMovie(uint16 id) { + debug(2, "Disabling movie %d", id); + for (uint16 i = 0; i < _mlstRecords.size(); i++) + if (_mlstRecords[i].code == id) + for (uint16 j = 0; j < _videoStreams.size(); j++) + if (_mlstRecords[i].movieID == _videoStreams[j].id) { + _videoStreams[j].enabled = false; + return; + } +} + +void VideoManager::disableAllMovies() { + debug(2, "Disabling all movies"); + for (uint16 i = 0; i < _videoStreams.size(); i++) + _videoStreams[i].enabled = false; +} + +VideoHandle VideoManager::createVideoHandle(uint16 id, uint16 x, uint16 y, bool loop) { + // First, check to see if that video is already playing + for (uint32 i = 0; i < _videoStreams.size(); i++) + if (_videoStreams[i].id == id) + return i; + + // Otherwise, create a new entry + VideoEntry entry; + entry.video = new QTPlayer(); + entry.x = x; + entry.y = y; + entry.filename = ""; + entry.id = id; + entry.loop = loop; + entry.enabled = true; + entry->setChunkBeginOffset(_vm->getResourceOffset(ID_TMOV, id)); + entry->load(*_vm->getRawData(ID_TMOV, id)); + + // Search for any deleted videos so we can take a formerly used slot + for (uint32 i = 0; i < _videoStreams.size(); i++) + if (!_videoStreams[i].video) { + _videoStreams[i] = entry; + return i; + } + + // Otherwise, just add it to the list + _videoStreams.push_back(entry); + return _videoStreams.size() - 1; +} + +VideoHandle VideoManager::createVideoHandle(Common::String filename, uint16 x, uint16 y, bool loop) { + // First, check to see if that video is already playing + for (uint32 i = 0; i < _videoStreams.size(); i++) + if (_videoStreams[i].filename == filename) + return i; + + // Otherwise, create a new entry + VideoEntry entry; + entry.video = new QTPlayer(); + entry.x = x; + entry.y = y; + entry.filename = filename; + entry.id = 0; + entry.loop = loop; + entry.enabled = true; + + Common::File *file = new Common::File(); + if (!file->open(filename)) { + delete file; + return NULL_VID_HANDLE; + } + + entry->load(*file); + + // Search for any deleted videos so we can take a formerly used slot + for (uint32 i = 0; i < _videoStreams.size(); i++) + if (!_videoStreams[i].video) { + _videoStreams[i] = entry; + return i; + } + + // Otherwise, just add it to the list + _videoStreams.push_back(entry); + return _videoStreams.size() - 1; +} + +} // End of namespace Mohawk diff --git a/engines/mohawk/video/video.h b/engines/mohawk/video/video.h new file mode 100644 index 0000000000..a5d2bde65d --- /dev/null +++ b/engines/mohawk/video/video.h @@ -0,0 +1,107 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef MOHAWK_VIDEO_H +#define MOHAWK_VIDEO_H + +#include "graphics/pixelformat.h" + +namespace Mohawk { + +class MohawkEngine; + +struct MLSTRecord { + uint16 index; + uint16 movieID; + uint16 code; + uint16 left; + uint16 top; + uint16 u0[3]; + uint16 loop; + uint16 volume; + uint16 u1; +}; + +class QTPlayer; + +struct VideoEntry { + QTPlayer *video; + uint16 x; + uint16 y; + bool loop; + Common::String filename; + uint16 id; // Riven only + bool enabled; + + QTPlayer *operator->() const { assert(video); return video; } +}; + +typedef int32 VideoHandle; + +enum { + NULL_VID_HANDLE = -1 +}; + +class VideoManager { +public: + VideoManager(MohawkEngine *vm); + ~VideoManager(); + + // Generic movie functions + void playMovie(Common::String filename, uint16 x = 0, uint16 y = 0, bool clearScreen = false); + void playMovieCentered(Common::String filename, bool clearScreen = true); + void playBackgroundMovie(Common::String filename, int16 x = -1, int16 y = -1, bool loop = false); + bool updateBackgroundMovies(); + void pauseVideos(); + void resumeVideos(); + void stopVideos(); + + // Riven-related functions + void activateMLST(uint16 mlstId, uint16 card); + void enableMovie(uint16 id); + void disableMovie(uint16 id); + void disableAllMovies(); + void playMovie(uint16 id); + void stopMovie(uint16 id); + void playMovieBlocking(uint16 id); + + // Riven-related variables + Common::Array<MLSTRecord> _mlstRecords; + +private: + MohawkEngine *_vm; + + void waitUntilMovieEnds(VideoHandle videoHandle); + + // Keep tabs on any videos playing + Common::Array<VideoEntry> _videoStreams; + + VideoHandle createVideoHandle(uint16 id, uint16 x, uint16 y, bool loop); + VideoHandle createVideoHandle(Common::String filename, uint16 x, uint16 y, bool loop); +}; + +} // End of namespace Mohawk + +#endif diff --git a/engines/parallaction/debug.cpp b/engines/parallaction/debug.cpp index b5eb82b456..8864c84e2f 100644 --- a/engines/parallaction/debug.cpp +++ b/engines/parallaction/debug.cpp @@ -42,7 +42,6 @@ Debugger::Debugger(Parallaction *vm) DCmd_Register("zones", WRAP_METHOD(Debugger, Cmd_Zones)); DCmd_Register("animations", WRAP_METHOD(Debugger, Cmd_Animations)); DCmd_Register("globalflags",WRAP_METHOD(Debugger, Cmd_GlobalFlags)); - DCmd_Register("toggleglobalflag",WRAP_METHOD(Debugger, Cmd_ToggleGlobalFlag)); DCmd_Register("localflags", WRAP_METHOD(Debugger, Cmd_LocalFlags)); DCmd_Register("locations", WRAP_METHOD(Debugger, Cmd_Locations)); DCmd_Register("gfxobjects", WRAP_METHOD(Debugger, Cmd_GfxObjects)); @@ -118,32 +117,6 @@ bool Debugger::Cmd_GlobalFlags(int argc, const char **argv) { return true; } -bool Debugger::Cmd_ToggleGlobalFlag(int argc, const char **argv) { - - int i; - - switch (argc) { - case 2: - i = _vm->_globalFlagsNames->lookup(argv[1]); - if (i == Table::notFound) { - DebugPrintf("invalid flag '%s'\n", argv[1]); - } else { - i--; - if ((_globalFlags & (1 << i)) == 0) - _globalFlags |= (1 << i); - else - _globalFlags &= ~(1 << i); - } - break; - - default: - DebugPrintf("toggleglobalflag <flag name>\n"); - - } - - return true; -} - bool Debugger::Cmd_LocalFlags(int argc, const char **argv) { uint32 flags = _vm->getLocationFlags(); diff --git a/engines/parallaction/debug.h b/engines/parallaction/debug.h index 5267206d04..54b578e95f 100644 --- a/engines/parallaction/debug.h +++ b/engines/parallaction/debug.h @@ -28,7 +28,6 @@ protected: bool Cmd_Animations(int argc, const char **argv); bool Cmd_LocalFlags(int argc, const char **argv); bool Cmd_GlobalFlags(int argc, const char **argv); - bool Cmd_ToggleGlobalFlag(int argc, const char **argv); bool Cmd_Locations(int argc, const char **argv); bool Cmd_GfxObjects(int argc, const char **argv); bool Cmd_Programs(int argc, const char** argv); diff --git a/engines/parallaction/dialogue.cpp b/engines/parallaction/dialogue.cpp index 7a28d18f17..6332600226 100644 --- a/engines/parallaction/dialogue.cpp +++ b/engines/parallaction/dialogue.cpp @@ -461,10 +461,6 @@ public: void Parallaction::enterDialogueMode(ZonePtr z) { - if (!z->u._speakDialogue) { - return; - } - debugC(1, kDebugDialogue, "Parallaction::enterDialogueMode(%s)", z->u._filename.c_str()); _dialogueMan = createDialogueManager(z); assert(_dialogueMan); diff --git a/engines/parallaction/exec_br.cpp b/engines/parallaction/exec_br.cpp index 1d8724e2d8..13c1318123 100644 --- a/engines/parallaction/exec_br.cpp +++ b/engines/parallaction/exec_br.cpp @@ -337,7 +337,7 @@ DECLARE_COMMAND_OPCODE(speak) { return; } - if (ACTIONTYPE(ctxt._cmd->_zone) == kZoneSpeak && ctxt._cmd->_zone->u._speakDialogue) { + if (ACTIONTYPE(ctxt._cmd->_zone) == kZoneSpeak) { _vm->enterDialogueMode(ctxt._cmd->_zone); } else { _vm->_activeZone = ctxt._cmd->_zone; diff --git a/engines/parallaction/graphics.cpp b/engines/parallaction/graphics.cpp index 2990d024d2..326ae2c519 100644 --- a/engines/parallaction/graphics.cpp +++ b/engines/parallaction/graphics.cpp @@ -538,12 +538,12 @@ GfxObj *Gfx::renderFloatingLabel(Font *font, char *text) { setupLabelSurface(*cnv, w, h); - font->setColor((_gameType == GType_BRA) ? 0 : 7); + font->setColor((_vm->getGameType() == GType_BRA) ? 0 : 7); font->drawString((byte*)cnv->pixels + 1, cnv->w, text); font->drawString((byte*)cnv->pixels + 1 + cnv->w * 2, cnv->w, text); font->drawString((byte*)cnv->pixels + cnv->w, cnv->w, text); font->drawString((byte*)cnv->pixels + 2 + cnv->w, cnv->w, text); - font->setColor((_gameType == GType_BRA) ? 11 : 1); + font->setColor((_vm->getGameType() == GType_BRA) ? 11 : 1); font->drawString((byte*)cnv->pixels + 1 + cnv->w, cnv->w, text); } else { w = font->getStringWidth(text); @@ -835,7 +835,7 @@ void Gfx::setBackground(uint type, BackgroundInfo *info) { // The PC version of BRA needs the entries 20-31 of the palette to be constant, but // the background resource files are screwed up. The right colors come from an unused // bitmap (pointer.bmp). Nothing is known about the Amiga version so far. - if ((_gameType == GType_BRA) && (_vm->getPlatform() == Common::kPlatformPC)) { + if ((_vm->getGameType() == GType_BRA) && (_vm->getPlatform() == Common::kPlatformPC)) { int r, g, b; for (uint i = 16; i < 32; i++) { _backupPal.getEntry(i, r, g, b); diff --git a/engines/parallaction/input.cpp b/engines/parallaction/input.cpp index ca8f358158..7ad1be8681 100644 --- a/engines/parallaction/input.cpp +++ b/engines/parallaction/input.cpp @@ -203,13 +203,13 @@ int Input::updateGameInput() { return event; } - if (_gameType == GType_Nippon) { + if (_vm->getGameType() == GType_Nippon) { if (_hasKeyPressEvent && (_vm->getFeatures() & GF_DEMO) == 0) { if (_keyPressed.keycode == Common::KEYCODE_l) event = kEvLoadGame; if (_keyPressed.keycode == Common::KEYCODE_s) event = kEvSaveGame; } } else - if (_gameType == GType_BRA) { + if (_vm->getGameType() == GType_BRA) { if (_hasKeyPressEvent && (_vm->getFeatures() & GF_DEMO) == 0) { if (_keyPressed.keycode == Common::KEYCODE_F5) event = kEvIngameMenu; } @@ -325,13 +325,8 @@ bool Input::translateGameInput() { if ((_mouseButtons == kMouseLeftUp) && ((_activeItem._id != 0) || (ACTIONTYPE(z) == kZoneCommand))) { - bool noWalk = z->_flags & kFlagsNoWalk; // check the explicit no-walk flag - if (_gameType == GType_BRA) { - // action performed on object marked for self-use do not need walk in BRA - noWalk |= ((z->_flags & kFlagsYourself) != 0); - } - - if (noWalk) { + if (z->_flags & kFlagsNoWalk) { + // character doesn't need to walk to take specified action takeAction(z); } else { // action delayed: if Zone defined a moveto position the character is programmed to move there, @@ -356,7 +351,7 @@ bool Input::translateGameInput() { void Input::enterInventoryMode() { Common::Point mousePos; - getAbsoluteCursorPos(mousePos); + getCursorPos(mousePos); bool hitCharacter = _vm->hitZone(kZoneYou, mousePos.x, mousePos.y); if (hitCharacter) { diff --git a/engines/parallaction/objects.h b/engines/parallaction/objects.h index 36231cfcc5..50a789247f 100644 --- a/engines/parallaction/objects.h +++ b/engines/parallaction/objects.h @@ -88,9 +88,9 @@ enum ZoneFlags { kFlagsNoWalk = 0x800, // Zone: character doesn't need to walk towards object to interact // BRA specific - kFlagsYourself = 0x1000, // BRA: marks zones used by the character on him/herself + kFlagsYourself = 0x1000, kFlagsScaled = 0x2000, - kFlagsSelfuse = 0x4000, // BRA: marks zones to be preserved across location changes (see Parallaction::freeZones) + kFlagsSelfuse = 0x4000, kFlagsIsAnimation = 0x1000000, // BRA: used in walk code (trap check), to tell is a Zone is an Animation kFlagsAnimLinked = 0x2000000 }; diff --git a/engines/parallaction/parallaction.cpp b/engines/parallaction/parallaction.cpp index ce7525345a..cb208a17ff 100644 --- a/engines/parallaction/parallaction.cpp +++ b/engines/parallaction/parallaction.cpp @@ -102,8 +102,7 @@ Parallaction::~Parallaction() { Common::Error Parallaction::init() { - - _gameType = getGameType(); + _engineFlags = 0; _objectsNames = NULL; _globalFlagsNames = NULL; @@ -409,7 +408,7 @@ void Parallaction::drawAnimation(AnimationPtr anim) { uint16 layer = LAYER_FOREGROUND; uint16 scale = 100; - switch (_gameType) { + switch (getGameType()) { case GType_Nippon: if ((anim->_flags & kFlagsNoMasked) == 0) { // Layer in NS depends on where the animation is on the screen, for each animation. @@ -524,7 +523,7 @@ void Parallaction::enterCommentMode(ZonePtr z) { } // TODO: move this balloons stuff into DialogueManager and BalloonManager - if (_gameType == GType_Nippon) { + if (getGameType() == GType_Nippon) { if (!data->_filename.empty()) { if (data->_gfxobj == 0) { data->_gfxobj = _disk->loadStatic(data->_filename.c_str()); @@ -541,7 +540,7 @@ void Parallaction::enterCommentMode(ZonePtr z) { _gfx->setItem(_char._talk, 190, 80); } } else - if (_gameType == GType_BRA) { + if (getGameType() == GType_BRA) { _balloonMan->setSingleBalloon(data->_examineText.c_str(), 0, 0, 1, BalloonManager::kNormalColor); _gfx->setItem(_char._talk, 10, 80); } @@ -652,21 +651,13 @@ bool Parallaction::pickupItem(ZonePtr z) { return (slot != -1); } +// FIXME: input coordinates must be offseted to handle scrolling! bool Parallaction::checkSpecialZoneBox(ZonePtr z, uint32 type, uint x, uint y) { - // check if really a special zone - if (_gameType == GType_Nippon) { - // so-called special zones in NS have special x coordinates - if ((z->getX() != -2) && (z->getX() != -3)) { - return false; - } - } - if (_gameType == GType_BRA) { - // so far, special zones in BRA are only merge zones - if (ACTIONTYPE(z) != kZoneMerge) { - return false; - } + // not a special zone + if ((z->getX() != -2) && (z->getX() != -3)) { + return false; } - + // WORKAROUND: this huge condition is needed because we made TypeData a collection of structs // instead of an union. So, merge->_obj1 and get->_icon were just aliases in the original engine, // but we need to check it separately here. The same workaround is applied in freeZones. @@ -690,33 +681,7 @@ bool Parallaction::checkSpecialZoneBox(ZonePtr z, uint32 type, uint x, uint y) { return false; } -bool Parallaction::checkZoneType(ZonePtr z, uint32 type) { - if (_gameType == GType_Nippon) { - if ((type == 0) && (ITEMTYPE(z) == 0)) - return true; - } - - if (_gameType == GType_BRA) { - if (type == 0) { - if (ITEMTYPE(z) == 0) { - if (ACTIONTYPE(z) != kZonePath) { - return true; - } - } - if (ACTIONTYPE(z) == kZoneDoor) { - return true; - } - } - } - - if (z->_type == type) - return true; - if (ITEMTYPE(z) == type) - return true; - - return false; -} - +// FIXME: input coordinates must be offseted to handle scrolling! bool Parallaction::checkZoneBox(ZonePtr z, uint32 type, uint x, uint y) { if (z->_flags & kFlagsRemove) return false; @@ -724,30 +689,29 @@ bool Parallaction::checkZoneBox(ZonePtr z, uint32 type, uint x, uint y) { debugC(5, kDebugExec, "checkZoneBox for %s (type = %x, x = %i, y = %i)", z->_name, type, x, y); if (!z->hitRect(x, y)) { + // check for special zones (items defined in common.loc) if (checkSpecialZoneBox(z, type, x, y)) return true; - // check if self-use zone (nothing to do with kFlagsSelfuse) - if (_gameType == GType_Nippon) { - if (z->getX() != -1) { // no explicit self-use flag in NS - return false; - } - } - if (_gameType == GType_BRA) { - if (!(z->_flags & kFlagsYourself)) { - return false; - } - } - if (!_char._ani->hitFrameRect(x, y)) { + if (z->getX() != -1) + return false; + if (!_char._ani->hitFrameRect(x, y)) return false; - } - // we get here only if (x,y) hits the character and the zone is marked as self-use } - return checkZoneType(z, type); + // normal Zone + if ((type == 0) && (ITEMTYPE(z) == 0)) + return true; + if (z->_type == type) + return true; + if (ITEMTYPE(z) == type) + return true; + + return false; } +// FIXME: input coordinates must be offseted to handle scrolling! bool Parallaction::checkLinkedAnimBox(ZonePtr z, uint32 type, uint x, uint y) { if (z->_flags & kFlagsRemove) return false; @@ -763,14 +727,18 @@ bool Parallaction::checkLinkedAnimBox(ZonePtr z, uint32 type, uint x, uint y) { return false; } - return checkZoneType(z, type); -} + // NOTE: the implementation of the following lines is a different in the + // original... it is working so far, though + if ((type == 0) && (ITEMTYPE(z) == 0)) + return true; + if (z->_type == type) + return true; + if (ITEMTYPE(z) == type) + return true; -/* NOTE: hitZone needs to be passed absolute game coordinates to work. + return false; +} - When type is kZoneMerge, then x and y are the identifiers of the objects to merge, - and the above requirement does not apply. -*/ ZonePtr Parallaction::hitZone(uint32 type, uint16 x, uint16 y) { uint16 _di = y; uint16 _si = x; @@ -784,20 +752,14 @@ ZonePtr Parallaction::hitZone(uint32 type, uint16 x, uint16 y) { } } + int16 _a, _b, _c, _d; bool _ef; for (AnimationList::iterator ait = _location._animations.begin(); ait != _location._animations.end(); ++ait) { AnimationPtr a = *ait; - _a = (a->_flags & kFlagsActive) ? 1 : 0; // _a: active Animation - - if (!_a) { - if (_gameType == GType_BRA && ACTIONTYPE(a) != kZoneTrap) { - continue; - } - } - + _a = (a->_flags & kFlagsActive) ? 1 : 0; // _a: active Animation _ef = a->hitFrameRect(_si, _di); _b = ((type != 0) || (a->_type == kZoneYou)) ? 0 : 1; // _b: (no type specified) AND (Animation is not the character) @@ -989,7 +951,7 @@ bool CharacterName::dummy() const { } void Parallaction::beep() { - if (_gameType == GType_Nippon) { + if (getGameType() == GType_Nippon) { _soundMan->execute(SC_SETSFXCHANNEL, 3); _soundMan->execute(SC_SETSFXVOLUME, 127); _soundMan->execute(SC_SETSFXLOOPING, (int32)0); diff --git a/engines/parallaction/parallaction.h b/engines/parallaction/parallaction.h index 7bbdf79f1c..3a84aa215e 100644 --- a/engines/parallaction/parallaction.h +++ b/engines/parallaction/parallaction.h @@ -280,7 +280,6 @@ public: int32 _screenWidth; int32 _screenHeight; int32 _screenSize; - int _gameType; // subsystems Gfx *_gfx; @@ -361,7 +360,6 @@ public: uint32 getLocationFlags(); bool checkSpecialZoneBox(ZonePtr z, uint32 type, uint x, uint y); bool checkZoneBox(ZonePtr z, uint32 type, uint x, uint y); - bool checkZoneType(ZonePtr z, uint32 type); bool checkLinkedAnimBox(ZonePtr z, uint32 type, uint x, uint y); ZonePtr hitZone(uint32 type, uint16 x, uint16 y); void runZone(ZonePtr z); diff --git a/engines/parallaction/parallaction_br.cpp b/engines/parallaction/parallaction_br.cpp index 470c698a21..ee718189b5 100644 --- a/engines/parallaction/parallaction_br.cpp +++ b/engines/parallaction/parallaction_br.cpp @@ -196,7 +196,7 @@ void Parallaction_br::runPendingZones() { if (_activeZone) { z = _activeZone; // speak Zone or sound _activeZone.reset(); - if (ACTIONTYPE(z) == kZoneSpeak && z->u._speakDialogue) { + if (ACTIONTYPE(z) == kZoneSpeak) { enterDialogueMode(z); } else { runZone(z); // FIXME: BRA doesn't handle sound yet @@ -206,7 +206,7 @@ void Parallaction_br::runPendingZones() { if (_activeZone2) { z = _activeZone2; // speak Zone or sound _activeZone2.reset(); - if (ACTIONTYPE(z) == kZoneSpeak && z->u._speakDialogue) { + if (ACTIONTYPE(z) == kZoneSpeak) { enterDialogueMode(z); } else { runZone(z); // FIXME: BRA doesn't handle sound yet diff --git a/engines/parallaction/parser.cpp b/engines/parallaction/parser.cpp index df1e91e8b4..928f3f5b74 100644 --- a/engines/parallaction/parser.cpp +++ b/engines/parallaction/parser.cpp @@ -44,10 +44,8 @@ Script::~Script() { /* * readLineIntern read a text line and prepares it for * parsing, by stripping the leading whitespace and - * changing tabs to spaces. It will stop on a CR, LF, or - * SUB (0x1A), which may all occur at the end of a script - * line. - * Returns an empty string (length = 0) when a line + * changing tabs to spaces. It will stop on a CR or LF, + * and return an empty string (length = 0) when a line * has no printable text in it. */ char *Script::readLineIntern(char *buf, size_t bufSize) { @@ -56,8 +54,7 @@ char *Script::readLineIntern(char *buf, size_t bufSize) { char c = _input->readSByte(); if (_input->eos()) break; - // break if EOL - if (c == '\n' || c == '\r' || c == (char)0x1A) + if (c == '\n' || c == '\r') break; if (c == '\t') c = ' '; diff --git a/engines/parallaction/parser_ns.cpp b/engines/parallaction/parser_ns.cpp index ff24a06ceb..be72cf73a1 100644 --- a/engines/parallaction/parser_ns.cpp +++ b/engines/parallaction/parser_ns.cpp @@ -286,7 +286,6 @@ void LocationParser_ns::parseAnimation(AnimationList &list, char *name) { debugC(5, kDebugParser, "parseAnimation(name: %s)", name); if (_vm->_location.findAnimation(name)) { - _zoneProg++; _script->skip("endanimation"); return; } @@ -1306,7 +1305,6 @@ void LocationParser_ns::parseZone(ZoneList &list, char *name) { debugC(5, kDebugParser, "parseZone(name: %s)", name); if (_vm->_location.findZone(name)) { - _zoneProg++; _script->skip("endzone"); return; } diff --git a/engines/parallaction/sound_br.cpp b/engines/parallaction/sound_br.cpp index 407dd86ec3..1c724ddc1c 100644 --- a/engines/parallaction/sound_br.cpp +++ b/engines/parallaction/sound_br.cpp @@ -172,11 +172,11 @@ bool MidiParser_MSC::loadMusic(byte *data, uint32 size) { byte *pos = data; - if (memcmp("MSCt", pos, 4)) { + uint32 signature = read4high(pos); + if (memcmp("tCSM", &signature, 4)) { warning("Expected header not found in music file."); return false; } - pos += 4; _beats = read1(pos); _ppqn = read2low(pos); diff --git a/engines/saga/saga.cpp b/engines/saga/saga.cpp index 1b7fa97f8d..025462c558 100644 --- a/engines/saga/saga.cpp +++ b/engines/saga/saga.cpp @@ -316,6 +316,20 @@ Common::Error SagaEngine::run() { syncSoundSettings(); + +#if 0 + // FIXME: Disabled this code for now. We want to get rid of OSystem::kFeatureAutoComputeDirtyRects + // and this is the last place to make use of it. We need to find out whether doing + // so causes any regressions. If it does, we can reenable it, if not, we can remove + // this code in 0.13.0. + + // FIXME: This is the ugly way of reducing redraw overhead. It works + // well for 320x200 but it's unclear how well it will work for + // 640x480. + if (getGameId() == GID_ITE) + _system->setFeatureState(OSystem::kFeatureAutoComputeDirtyRects, true); +#endif + int msec = 0; _previousTicks = _system->getMillis(); diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index ef7b7dcb41..e5fcbf72c2 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -52,7 +52,7 @@ struct SciScriptSignature { // - rinse and repeat -// daySixBeignet::changeState (4) is called when the cop goes out and sets cycles to 220. +// daySixBeignet::changeState is called when the cop goes out and sets cycles to 220. // this is not enough time to get to the door, so we patch that to 23 seconds const byte gk1SignatureDay6PoliceBeignet[] = { 4, @@ -78,8 +78,6 @@ const uint16 gk1PatchDay6PoliceBeignet[] = { PATCH_END }; -// sargSleeping::changeState (8) is called when the cop falls asleep and sets cycles to 220. -// this is not enough time to get to the door, so we patch it to 42 seconds const byte gk1SignatureDay6PoliceSleep[] = { 4, 0x35, 0x08, // ldi 08 @@ -99,27 +97,8 @@ const uint16 gk1PatchDay6PoliceSleep[] = { PATCH_END }; -// startOfDay5::changeState (20h) - when gabriel goes to the phone the script will hang -const byte gk1SignatureDay5PhoneFreeze[] = { - 5, - 0x35, 0x03, // ldi 03 - 0x65, 0x1a, // aTop cycles - 0x32, // jmp [end] - +2, 3, // [skip 2 bytes, offset of jmp] - 0x3c, // dup - 0x35, 0x21, // ldi 21 - 0 -}; - -const uint16 gk1PatchDay5PhoneFreeze[] = { - 0x35, 0x06, // ldi 06 - 0x65, 0x20, // aTop ticks - PATCH_END -}; - // script, description, magic DWORD, adjust const SciScriptSignature gk1Signatures[] = { - { 212, "day 5 phone freeze", PATCH_MAGICDWORD(0x35, 0x03, 0x65, 0x1a), 0, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze }, { 230, "day 6 police beignet timer issue", PATCH_MAGICDWORD(0x34, 0xdc, 0x00, 0x65), -16, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet }, { 230, "day 6 police sleep timer issue", PATCH_MAGICDWORD(0x34, 0xdc, 0x00, 0x65), -5, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep }, { 0, NULL, 0, 0, NULL, NULL } diff --git a/engines/sci/sound/iterator/core.cpp b/engines/sci/sound/iterator/core.cpp new file mode 100644 index 0000000000..7cd730b3e2 --- /dev/null +++ b/engines/sci/sound/iterator/core.cpp @@ -0,0 +1,1013 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +/* Sound subsystem core: Event handler, sound player dispatching */ + +#include "sci/sci.h" +#ifdef USE_OLD_MUSIC_FUNCTIONS + +#include "sci/sound/iterator/core.h" +#include "sci/sound/iterator/iterator.h" +#include "sci/sound/drivers/mididriver.h" + +#include "common/system.h" +#include "common/timer.h" + +#include "sound/mixer.h" + +namespace Sci { + +/* Plays a song iterator that found a PCM through a PCM device, if possible +** Parameters: (SongIterator *) it: The iterator to play +** (SongHandle) handle: Debug handle +** Returns : (int) 0 if the effect will not be played, nonzero if it will +** This assumes that the last call to 'it->next()' returned SI_PCM. +*/ +static int sfx_play_iterator_pcm(SongIterator *it, SongHandle handle); + + +#pragma mark - + + +class SfxPlayer { +public: + /** Number of voices that can play simultaneously */ + int _polyphony; + +protected: + SciVersion _soundVersion; + MidiPlayer *_mididrv; + + SongIterator *_iterator; + Audio::Timestamp _wakeupTime; + Audio::Timestamp _currentTime; + uint32 _pauseTimeDiff; + + bool _paused; + bool _iteratorIsDone; + uint32 _tempo; + + Common::Mutex _mutex; + int _volume; + + void play_song(SongIterator *it); + static void player_timer_callback(void *refCon); + +public: + SfxPlayer(SciVersion soundVersion); + ~SfxPlayer(); + + /** + * Initializes the player. + * @param resMan a resource manager for driver initialization + * @param expected_latency expected delay in between calls to 'maintenance' (in microseconds) + * @return Common::kNoError on success, Common::kUnknownError on failure + */ + Common::Error init(ResourceManager *resMan, int expected_latency); + + /** + * Adds an iterator to the song player + * @param it The iterator to play + * @param start_time The time to assume as the time the first MIDI command executes at + * @return Common::kNoError on success, Common::kUnknownError on failure + * + * The iterator should not be cloned (to avoid memory leaks) and + * may be modified according to the needs of the player. + * Implementors may use the 'sfx_iterator_combine()' function + * to add iterators onto their already existing iterators. + */ + Common::Error add_iterator(SongIterator *it, uint32 start_time); + + /** + * Stops the currently playing song and deletes the associated iterator. + * @return Common::kNoError on success, Common::kUnknownError on failure + */ + Common::Error stop(); + + /** + * Transmits a song iterator message to the active song. + * @param msg the message to transmit + * @return Common::kNoError on success, Common::kUnknownError on failure + */ + Common::Error iterator_message(const SongIterator::Message &msg); + + /** + * Pauses song playing. + * @return Common::kNoError on success, Common::kUnknownError on failure + */ + Common::Error pause(); + + /** + * Resumes song playing after a pause. + * @return Common::kNoError on success, Common::kUnknownError on failure + */ + Common::Error resume(); + + /** + * Pass a raw MIDI event to the synth. + * @param argc length of buffer holding the midi event + * @param argv the buffer itself + */ + void tell_synth(int buf_nr, byte *buf); + + void setVolume(int vol); + + int getVolume(); +}; + +SfxPlayer::SfxPlayer(SciVersion soundVersion) + : _soundVersion(soundVersion), _wakeupTime(0, SFX_TICKS_PER_SEC), _currentTime(0, 1) { + _polyphony = 0; + + _mididrv = 0; + + _iterator = NULL; + _pauseTimeDiff = 0; + + _paused = false; + _iteratorIsDone = false; + _tempo = 0; + + _volume = 15; +} + +SfxPlayer::~SfxPlayer() { + if (_mididrv) { + _mididrv->close(); + delete _mididrv; + } + delete _iterator; + _iterator = NULL; +} + +void SfxPlayer::play_song(SongIterator *it) { + while (_iterator && _wakeupTime.msecsDiff(_currentTime) <= 0) { + int delay; + byte buf[8]; + int result; + + switch ((delay = songit_next(&(_iterator), + buf, &result, + IT_READER_MASK_ALL + | IT_READER_MAY_FREE + | IT_READER_MAY_CLEAN))) { + + case SI_FINISHED: + delete _iterator; + _iterator = NULL; + _iteratorIsDone = true; + return; + + case SI_IGNORE: + case SI_LOOP: + case SI_RELATIVE_CUE: + case SI_ABSOLUTE_CUE: + break; + + case SI_PCM: + sfx_play_iterator_pcm(_iterator, 0); + break; + + case 0: + static_cast<MidiDriver *>(_mididrv)->send(buf[0], buf[1], buf[2]); + + break; + + default: + _wakeupTime = _wakeupTime.addFrames(delay); + } + } +} + +void SfxPlayer::tell_synth(int buf_nr, byte *buf) { + byte op1 = (buf_nr < 2 ? 0 : buf[1]); + byte op2 = (buf_nr < 3 ? 0 : buf[2]); + + static_cast<MidiDriver *>(_mididrv)->send(buf[0], op1, op2); +} + +void SfxPlayer::player_timer_callback(void *refCon) { + SfxPlayer *thePlayer = (SfxPlayer *)refCon; + assert(refCon); + Common::StackLock lock(thePlayer->_mutex); + + if (thePlayer->_iterator && !thePlayer->_iteratorIsDone && !thePlayer->_paused) { + thePlayer->play_song(thePlayer->_iterator); + } + + thePlayer->_currentTime = thePlayer->_currentTime.addFrames(1); +} + +/* API implementation */ + +Common::Error SfxPlayer::init(ResourceManager *resMan, int expected_latency) { + MidiDriverType musicDriver = MidiDriver::detectMusicDriver(MDT_PCSPK | MDT_ADLIB); + + switch (musicDriver) { + case MD_ADLIB: + // FIXME: There's no Amiga sound option, so we hook it up to AdLib + if (g_sci->getPlatform() == Common::kPlatformAmiga) + _mididrv = MidiPlayer_Amiga_create(_soundVersion); + else + _mididrv = MidiPlayer_AdLib_create(_soundVersion); + break; + case MD_PCJR: + _mididrv = MidiPlayer_PCJr_create(_soundVersion); + break; + case MD_PCSPK: + _mididrv = MidiPlayer_PCSpeaker_create(_soundVersion); + break; + default: + break; + } + + assert(_mididrv); + + _polyphony = _mididrv->getPolyphony(); + + _tempo = _mididrv->getBaseTempo(); + uint32 time = g_system->getMillis(); + _currentTime = Audio::Timestamp(time, 1000000 / _tempo); + _wakeupTime = Audio::Timestamp(time, SFX_TICKS_PER_SEC); + + _mididrv->setTimerCallback(this, player_timer_callback); + _mididrv->open(resMan); + _mididrv->setVolume(_volume); + + return Common::kNoError; +} + +Common::Error SfxPlayer::add_iterator(SongIterator *it, uint32 start_time) { + Common::StackLock lock(_mutex); + SIMSG_SEND(it, SIMSG_SET_PLAYMASK(_mididrv->getPlayId())); + SIMSG_SEND(it, SIMSG_SET_RHYTHM(_mididrv->hasRhythmChannel())); + + if (_iterator == NULL) { + // Resync with clock + _currentTime = Audio::Timestamp(g_system->getMillis(), 1000000 / _tempo); + _wakeupTime = Audio::Timestamp(start_time, SFX_TICKS_PER_SEC); + } + + _iterator = sfx_iterator_combine(_iterator, it); + _iteratorIsDone = false; + + return Common::kNoError; +} + +Common::Error SfxPlayer::stop() { + debug(3, "Player: Stopping song iterator %p", (void *)_iterator); + Common::StackLock lock(_mutex); + delete _iterator; + _iterator = NULL; + for (int i = 0; i < MIDI_CHANNELS; i++) + static_cast<MidiDriver *>(_mididrv)->send(0xb0 + i, SCI_MIDI_CHANNEL_NOTES_OFF, 0); + + return Common::kNoError; +} + +Common::Error SfxPlayer::iterator_message(const SongIterator::Message &msg) { + Common::StackLock lock(_mutex); + if (!_iterator) { + return Common::kUnknownError; + } + + songit_handle_message(&_iterator, msg); + + return Common::kNoError; +} + +Common::Error SfxPlayer::pause() { + Common::StackLock lock(_mutex); + + _paused = true; + _pauseTimeDiff = _wakeupTime.msecsDiff(_currentTime); + + _mididrv->playSwitch(false); + + return Common::kNoError; +} + +Common::Error SfxPlayer::resume() { + Common::StackLock lock(_mutex); + + _wakeupTime = Audio::Timestamp(_currentTime.msecs() + _pauseTimeDiff, SFX_TICKS_PER_SEC); + _mididrv->playSwitch(true); + _paused = false; + + return Common::kNoError; +} + +void SfxPlayer::setVolume(int vol) { + _mididrv->setVolume(vol); +} + +int SfxPlayer::getVolume() { + return _mididrv->getVolume(); +} + +#pragma mark - + +void SfxState::sfx_reset_player() { + if (_player) + _player->stop(); +} + +void SfxState::sfx_player_tell_synth(int buf_nr, byte *buf) { + if (_player) + _player->tell_synth(buf_nr, buf); +} + +int SfxState::sfx_get_player_polyphony() { + if (_player) + return _player->_polyphony; + else + return 0; +} + +SfxState::SfxState() { + _player = NULL; + _it = NULL; + _flags = 0; + _song = NULL; + _suspended = 0; +} + +SfxState::~SfxState() { +} + + +void SfxState::freezeTime() { + /* Freezes the top song delay time */ + const Audio::Timestamp ctime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC); + Song *song = _song; + + while (song) { + song->_delay = song->_wakeupTime.frameDiff(ctime); + if (song->_delay < 0) + song->_delay = 0; + + song = song->_nextPlaying; + } +} + +void SfxState::thawTime() { + /* inverse of freezeTime() */ + const Audio::Timestamp ctime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC); + Song *song = _song; + + while (song) { + song->_wakeupTime = ctime.addFrames(song->_delay); + + song = song->_nextPlaying; + } +} + +#if 0 +// Unreferenced - removed +static void _dump_playing_list(SfxState *self, char *msg) { + Song *song = self->_song; + + fprintf(stderr, "[] Song list : [ "); + song = *(self->_songlib.lib); + while (song) { + fprintf(stderr, "%08lx:%d ", song->handle, song->_status); + song = song->_nextPlaying; + } + fprintf(stderr, "]\n"); + + fprintf(stderr, "[] Play list (%s) : [ " , msg); + + while (song) { + fprintf(stderr, "%08lx ", song->handle); + song = song->_nextPlaying; + } + + fprintf(stderr, "]\n"); +} +#endif + +#if 0 +static void _dump_songs(SfxState *self) { + Song *song = self->_song; + + fprintf(stderr, "Cue iterators:\n"); + song = *(self->_songlib.lib); + while (song) { + fprintf(stderr, " **\tHandle %08x (p%d): status %d\n", + song->handle, song->_priority, song->_status); + SIMSG_SEND(song->_it, SIMSG_PRINT(1)); + song = song->_next; + } + + if (self->_player) { + fprintf(stderr, "Audio iterator:\n"); + self->_player->iterator_message(SongIterator::Message(0, SIMSG_PRINT(1))); + } +} +#endif + +bool SfxState::isPlaying(Song *song) { + Song *playing_song = _song; + + /* _dump_playing_list(this, "is-playing");*/ + + while (playing_song) { + if (playing_song == song) + return true; + playing_song = playing_song->_nextPlaying; + } + return false; +} + +void SfxState::setSongStatus(Song *song, int status) { + const Audio::Timestamp ctime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC); + + switch (status) { + + case SOUND_STATUS_STOPPED: + // Reset + song->_it->init(); + break; + + case SOUND_STATUS_SUSPENDED: + case SOUND_STATUS_WAITING: + if (song->_status == SOUND_STATUS_PLAYING) { + // Update delay, set wakeup_time + song->_delay += song->_wakeupTime.frameDiff(ctime); + song->_wakeupTime = ctime; + } + if (status == SOUND_STATUS_SUSPENDED) + break; + + /* otherwise... */ + + case SOUND_STATUS_PLAYING: + if (song->_status == SOUND_STATUS_STOPPED) { + // Starting anew + song->_wakeupTime = ctime; + } + + if (isPlaying(song)) + status = SOUND_STATUS_PLAYING; + else + status = SOUND_STATUS_WAITING; + break; + + default: + fprintf(stderr, "%s L%d: Attempt to set invalid song" + " state %d!\n", __FILE__, __LINE__, status); + return; + + } + song->_status = status; +} + +/* Update internal state iff only one song may be played */ +void SfxState::updateSingleSong() { + Song *newsong = _songlib.findFirstActive(); + + if (newsong != _song) { + freezeTime(); /* Store song delay time */ + + if (_player) + _player->stop(); + + if (newsong) { + if (!newsong->_it) + return; /* Restore in progress and not ready for this yet */ + + /* Change song */ + if (newsong->_status == SOUND_STATUS_WAITING) + setSongStatus(newsong, SOUND_STATUS_PLAYING); + + /* Change instrument mappings */ + } else { + /* Turn off sound */ + } + if (_song) { + if (_song->_status == SOUND_STATUS_PLAYING) + setSongStatus(newsong, SOUND_STATUS_WAITING); + } + + Common::String debugMessage = "[SFX] Changing active song:"; + if (!_song) { + debugMessage += " New song:"; + } else { + char tmp[50]; + sprintf(tmp, " pausing %08lx, now playing ", _song->_handle); + debugMessage += tmp; + } + + if (newsong) { + char tmp[20]; + sprintf(tmp, "%08lx\n", newsong->_handle); + debugMessage += tmp; + } else { + debugMessage += " none\n"; + } + + debugC(2, kDebugLevelSound, "%s", debugMessage.c_str()); + + _song = newsong; + thawTime(); /* Recover song delay time */ + + if (newsong && _player) { + SongIterator *clonesong = newsong->_it->clone(newsong->_delay); + + _player->add_iterator(clonesong, newsong->_wakeupTime.msecs()); + } + } +} + + +void SfxState::updateMultiSong() { + Song *oldfirst = _song; + Song *oldseeker; + Song *newsong = _songlib.findFirstActive(); + Song *newseeker; + Song not_playing_anymore; /* Dummy object, referenced by + ** songs which are no longer + ** active. */ + + /* _dump_playing_list(this, "before");*/ + freezeTime(); /* Store song delay time */ + + // WORKAROUND: sometimes, newsong can be NULL (e.g. in SQ4). + // Handle this here, so that we avoid a crash + if (!newsong) { + // Iterators should get freed when there's only one song left playing + if(oldfirst && oldfirst->_status == SOUND_STATUS_STOPPED) { + debugC(2, kDebugLevelSound, "[SFX] Stopping song %lx", oldfirst->_handle); + if (_player && oldfirst->_it) + _player->iterator_message(SongIterator::Message(oldfirst->_it->ID, SIMSG_STOP)); + } + return; + } + + for (newseeker = newsong; newseeker; + newseeker = newseeker->_nextPlaying) { + if (!newseeker || !newseeker->_it) + return; /* Restore in progress and not ready for this yet */ + } + + /* First, put all old songs into the 'stopping' list and + ** mark their 'next-playing' as not_playing_anymore. */ + for (oldseeker = oldfirst; oldseeker; + oldseeker = oldseeker->_nextStopping) { + oldseeker->_nextStopping = oldseeker->_nextPlaying; + oldseeker->_nextPlaying = ¬_playing_anymore; + + if (oldseeker == oldseeker->_nextPlaying) { + error("updateMultiSong() failed. Breakpoint in %s, line %d", __FILE__, __LINE__); + } + } + + /* Second, re-generate the new song queue. */ + for (newseeker = newsong; newseeker; newseeker = newseeker->_nextPlaying) { + newseeker->_nextPlaying = _songlib.findNextActive(newseeker); + + if (newseeker == newseeker->_nextPlaying) { + error("updateMultiSong() failed. Breakpoint in %s, line %d", __FILE__, __LINE__); + } + } + /* We now need to update the currently playing song list, because we're + ** going to use some functions that require this list to be in a sane + ** state (particularly isPlaying(), indirectly */ + _song = newsong; + + /* Third, stop all old songs */ + for (oldseeker = oldfirst; oldseeker; + oldseeker = oldseeker->_nextStopping) + if (oldseeker->_nextPlaying == ¬_playing_anymore) { + setSongStatus(oldseeker, SOUND_STATUS_SUSPENDED); + debugC(2, kDebugLevelSound, "[SFX] Stopping song %lx", oldseeker->_handle); + + if (_player && oldseeker->_it) + _player->iterator_message(SongIterator::Message(oldseeker->_it->ID, SIMSG_STOP)); + oldseeker->_nextPlaying = NULL; /* Clear this pointer; we don't need the tag anymore */ + } + + for (newseeker = newsong; newseeker; newseeker = newseeker->_nextPlaying) { + if (newseeker->_status != SOUND_STATUS_PLAYING && _player) { + debugC(2, kDebugLevelSound, "[SFX] Adding song %lx", newseeker->_it->ID); + + SongIterator *clonesong = newseeker->_it->clone(newseeker->_delay); + _player->add_iterator(clonesong, g_system->getMillis()); + } + setSongStatus(newseeker, SOUND_STATUS_PLAYING); + } + + _song = newsong; + thawTime(); + /* _dump_playing_list(this, "after");*/ +} + +/* Update internal state */ +void SfxState::update() { + if (_flags & SFX_STATE_FLAG_MULTIPLAY) + updateMultiSong(); + else + updateSingleSong(); +} + +static int sfx_play_iterator_pcm(SongIterator *it, SongHandle handle) { +#ifdef DEBUG_SONG_API + fprintf(stderr, "[sfx-core] Playing PCM: %08lx\n", handle); +#endif + if (g_system->getMixer()->isReady()) { + Audio::AudioStream *newfeed = it->getAudioStream(); + if (newfeed) { + g_system->getMixer()->playStream(Audio::Mixer::kSFXSoundType, 0, newfeed); + return 1; + } + } + return 0; +} + +#define DELAY (1000000 / SFX_TICKS_PER_SEC) + +void SfxState::sfx_init(ResourceManager *resMan, int flags, SciVersion soundVersion) { + _songlib._lib = 0; + _song = NULL; + _flags = flags; + + _player = NULL; + + if (flags & SFX_STATE_FLAG_NOSOUND) { + warning("[SFX] Sound disabled"); + return; + } + +#ifdef DEBUG_SONG_API + fprintf(stderr, "[sfx-core] Initialising: flags=%x\n", flags); +#endif + + /*-------------------*/ + /* Initialise player */ + /*-------------------*/ + + if (!resMan) { + warning("[SFX] Warning: No resource manager present, cannot initialise player"); + return; + } + + _player = new SfxPlayer(soundVersion); + + if (!_player) { + warning("[SFX] No song player found"); + return; + } + + if (_player->init(resMan, DELAY / 1000)) { + warning("[SFX] Song player reported error, disabled"); + delete _player; + _player = NULL; + } + + _resMan = resMan; +} + +void SfxState::sfx_exit() { +#ifdef DEBUG_SONG_API + fprintf(stderr, "[sfx-core] Uninitialising\n"); +#endif + + delete _player; + _player = 0; + + g_system->getMixer()->stopAll(); + + _songlib.freeSounds(); +} + +void SfxState::sfx_suspend(bool suspend) { +#ifdef DEBUG_SONG_API + fprintf(stderr, "[sfx-core] Suspending? = %d\n", suspend); +#endif + if (suspend && (!_suspended)) { + /* suspend */ + + freezeTime(); + if (_player) + _player->pause(); + /* Suspend song player */ + + } else if (!suspend && (_suspended)) { + /* unsuspend */ + + thawTime(); + if (_player) + _player->resume(); + + /* Unsuspend song player */ + } + + _suspended = suspend; +} + +int SfxState::sfx_poll(SongHandle *handle, int *cue) { + if (!_song) + return 0; /* No milk today */ + + *handle = _song->_handle; + +#ifdef DEBUG_SONG_API + fprintf(stderr, "[sfx-core] Polling any (%08lx)\n", *handle); +#endif + return sfx_poll_specific(*handle, cue); +} + +int SfxState::sfx_poll_specific(SongHandle handle, int *cue) { + const Audio::Timestamp ctime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC); + Song *song = _song; + + while (song && song->_handle != handle) + song = song->_nextPlaying; + + if (!song) + return 0; /* Song not playing */ + + debugC(2, kDebugLevelSound, "[SFX:CUE] Polled song %08lx ", handle); + + while (1) { + if (song->_wakeupTime.frameDiff(ctime) > 0) + return 0; /* Patience, young hacker! */ + + byte buf[8]; + int result = songit_next(&(song->_it), buf, cue, IT_READER_MASK_ALL); + + switch (result) { + + case SI_FINISHED: + setSongStatus(song, SOUND_STATUS_STOPPED); + update(); + /* ...fall through... */ + case SI_LOOP: + case SI_RELATIVE_CUE: + case SI_ABSOLUTE_CUE: + if (result == SI_FINISHED) + debugC(2, kDebugLevelSound, " => finished"); + else { + if (result == SI_LOOP) + debugC(2, kDebugLevelSound, " => Loop: %d (0x%x)", *cue, *cue); + else + debugC(2, kDebugLevelSound, " => Cue: %d (0x%x)", *cue, *cue); + + } + return result; + + default: + if (result > 0) + song->_wakeupTime = song->_wakeupTime.addFrames(result); + + /* Delay */ + break; + } + } + +} + + +/*****************/ +/* Song basics */ +/*****************/ + +void SfxState::sfx_add_song(SongIterator *it, int priority, SongHandle handle, int number) { + Song *song = _songlib.findSong(handle); + +#ifdef DEBUG_SONG_API + fprintf(stderr, "[sfx-core] Adding song: %08lx at %d, it=%p\n", handle, priority, it); +#endif + if (!it) { + error("[SFX] Attempt to add empty song with handle %08lx", handle); + return; + } + + it->init(); + + /* If we're already playing this, stop it */ + /* Tell player to shut up */ +// _dump_songs(this); + + if (_player) + _player->iterator_message(SongIterator::Message(handle, SIMSG_STOP)); + + if (song) { + setSongStatus( song, SOUND_STATUS_STOPPED); + + fprintf(stderr, "Overwriting old song (%08lx) ...\n", handle); + if (song->_status == SOUND_STATUS_PLAYING || song->_status == SOUND_STATUS_SUSPENDED) { + delete it; + error("Unexpected (error): Song %ld still playing/suspended (%d)", + handle, song->_status); + return; + } else { + _songlib.removeSong(handle); /* No duplicates */ + } + + } + + song = new Song(handle, it, priority); + song->_resourceNum = number; + song->_hold = 0; + song->_loops = 0; + song->_wakeupTime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC); + _songlib.addSong(song); + _song = NULL; /* As above */ + update(); + + return; +} + +void SfxState::sfx_remove_song(SongHandle handle) { +#ifdef DEBUG_SONG_API + fprintf(stderr, "[sfx-core] Removing song: %08lx\n", handle); +#endif + if (_song && _song->_handle == handle) + _song = NULL; + + _songlib.removeSong(handle); + update(); +} + + + +/**********************/ +/* Song modifications */ +/**********************/ + +#define ASSERT_SONG(s) if (!(s)) { warning("Looking up song handle %08lx failed in %s, L%d", handle, __FILE__, __LINE__); return; } + +void SfxState::sfx_song_set_status(SongHandle handle, int status) { + Song *song = _songlib.findSong(handle); + ASSERT_SONG(song); +#ifdef DEBUG_SONG_API + fprintf(stderr, "[sfx-core] Setting song status to %d" + " (0:stop, 1:play, 2:susp, 3:wait): %08lx\n", status, handle); +#endif + + setSongStatus(song, status); + + update(); +} + +void SfxState::sfx_song_set_fade(SongHandle handle, fade_params_t *params) { +#ifdef DEBUG_SONG_API + static const char *stopmsg[] = {"??? Should not happen", "Do not stop afterwards", "Stop afterwards"}; +#endif + Song *song = _songlib.findSong(handle); + + ASSERT_SONG(song); + +#ifdef DEBUG_SONG_API + fprintf(stderr, "[sfx-core] Setting fade params of %08lx to " + "final volume %d in steps of %d per %d ticks. %s.", + handle, fade->final_volume, fade->step_size, fade->ticks_per_step, + stopmsg[fade->action]); +#endif + + SIMSG_SEND_FADE(song->_it, params); + + update(); +} + +void SfxState::sfx_song_renice(SongHandle handle, int priority) { + Song *song = _songlib.findSong(handle); + ASSERT_SONG(song); +#ifdef DEBUG_SONG_API + fprintf(stderr, "[sfx-core] Renicing song %08lx to %d\n", + handle, priority); +#endif + + song->_priority = priority; + + update(); +} + +void SfxState::sfx_song_set_loops(SongHandle handle, int loops) { + Song *song = _songlib.findSong(handle); + SongIterator::Message msg = SongIterator::Message(handle, SIMSG_SET_LOOPS(loops)); + ASSERT_SONG(song); + + song->_loops = loops; +#ifdef DEBUG_SONG_API + fprintf(stderr, "[sfx-core] Setting loops on %08lx to %d\n", + handle, loops); +#endif + songit_handle_message(&(song->_it), msg); + + if (_player/* && _player->send_iterator_message*/) + /* FIXME: The above should be optional! */ + _player->iterator_message(msg); +} + +void SfxState::sfx_song_set_hold(SongHandle handle, int hold) { + Song *song = _songlib.findSong(handle); + SongIterator::Message msg = SongIterator::Message(handle, SIMSG_SET_HOLD(hold)); + ASSERT_SONG(song); + + song->_hold = hold; +#ifdef DEBUG_SONG_API + fprintf(stderr, "[sfx-core] Setting hold on %08lx to %d\n", + handle, hold); +#endif + songit_handle_message(&(song->_it), msg); + + if (_player/* && _player->send_iterator_message*/) + /* FIXME: The above should be optional! */ + _player->iterator_message(msg); +} + +/* Different from the one in iterator.c */ +static const int MIDI_cmdlen[16] = {0, 0, 0, 0, 0, 0, 0, 0, + 3, 3, 0, 3, 2, 0, 3, 0 + }; + +static const SongHandle midi_send_base = 0xffff0000; + +Common::Error SfxState::sfx_send_midi(SongHandle handle, int channel, + int command, int arg1, int arg2) { + byte buffer[5]; + + /* Yes, in that order. SCI channel mutes are actually done via + a counting semaphore. 0 means to decrement the counter, 1 + to increment it. */ + static const char *channel_state[] = {"ON", "OFF"}; + + if (command == 0xb0 && + arg1 == SCI_MIDI_CHANNEL_MUTE) { + warning("TODO: channel mute (channel %d %s)", channel, channel_state[arg2]); + /* We need to have a GET_PLAYMASK interface to use + here. SET_PLAYMASK we've got. + */ + return Common::kNoError; + } + + buffer[0] = channel | command; /* No channel remapping yet */ + + switch (command) { + case 0x80 : + case 0x90 : + case 0xb0 : + buffer[1] = arg1 & 0xff; + buffer[2] = arg2 & 0xff; + break; + case 0xc0 : + buffer[1] = arg1 & 0xff; + break; + case 0xe0 : + buffer[1] = (arg1 & 0x7f) | 0x80; + buffer[2] = (arg1 & 0xff00) >> 7; + break; + default: + warning("Unexpected explicit MIDI command %02x", command); + return Common::kUnknownError; + } + + if (_player) + _player->tell_synth(MIDI_cmdlen[command >> 4], buffer); + return Common::kNoError; +} + +int SfxState::sfx_getVolume() { + return _player->getVolume(); +} + +void SfxState::sfx_setVolume(int volume) { + _player->setVolume(volume); +} + +void SfxState::sfx_all_stop() { +#ifdef DEBUG_SONG_API + fprintf(stderr, "[sfx-core] All stop\n"); +#endif + + _songlib.freeSounds(); + update(); +} + +} // End of namespace Sci + +#endif // USE_OLD_MUSIC_FUNCTIONS diff --git a/engines/sci/sound/iterator/core.h b/engines/sci/sound/iterator/core.h new file mode 100644 index 0000000000..a44fe2ecae --- /dev/null +++ b/engines/sci/sound/iterator/core.h @@ -0,0 +1,209 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +/* Sound engine */ +#ifndef SCI_SFX_CORE_H +#define SCI_SFX_CORE_H + +#include "common/error.h" + +#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS + +#ifdef USE_OLD_MUSIC_FUNCTIONS +#include "sci/sound/iterator/songlib.h" +#include "sci/resource.h" + +namespace Sci { + +class SfxPlayer; +class SongIterator; +struct fade_params_t; + +#define SFX_TICKS_PER_SEC 60 /* MIDI ticks per second */ + + +#define SFX_STATE_FLAG_MULTIPLAY (1 << 0) /* More than one song playable +** simultaneously ? */ +#define SFX_STATE_FLAG_NOSOUND (1 << 1) /* Completely disable sound playing */ + +class SfxState { +private: + SfxPlayer *_player; + +public: // FIXME, make private + SongIterator *_it; /**< The song iterator at the heart of things */ + uint _flags; /**< SFX_STATE_FLAG_* */ + SongLibrary _songlib; /**< Song library */ + Song *_song; /**< Active song, or start of active song chain */ + bool _suspended; /**< Whether we are suspended */ + ResourceManager *_resMan; + +public: + SfxState(); + ~SfxState(); + + /***********/ + /* General */ + /***********/ + + /* Initializes the sound engine + ** Parameters: (ResourceManager *) resMan: Resource manager for initialization + ** (int) flags: SFX_STATE_FLAG_* + */ + void sfx_init(ResourceManager *resMan, int flags, SciVersion soundVersion); + + /** Deinitializes the sound subsystem. */ + void sfx_exit(); + + /* Suspends/unsuspends the sound sybsystem + ** Parameters: (int) suspend: Whether to suspend (non-null) or to unsuspend + */ + void sfx_suspend(bool suspend); + + /* Polls the sound server for cues etc. + ** Returns : (int) 0 if the cue queue is empty, SI_LOOP, SI_CUE, or SI_FINISHED otherwise + ** (SongHandle) *handle: The affected handle + ** (int) *cue: The sound cue number (if SI_CUE), or the loop number (if SI_LOOP) + */ + int sfx_poll(SongHandle *handle, int *cue); + + /* Polls the sound server for cues etc. + ** Parameters: (SongHandle) handle: The handle to poll + ** Returns : (int) 0 if the cue queue is empty, SI_LOOP, SI_CUE, or SI_FINISHED otherwise + ** (int) *cue: The sound cue number (if SI_CUE), or the loop number (if SI_LOOP) + */ + int sfx_poll_specific(SongHandle handle, int *cue); + + /* Determines the current global volume settings + ** Returns : (int) The global volume, between 0 (silent) and 127 (max. volume) + */ + int sfx_getVolume(); + + /* Determines the current global volume settings + ** Parameters: (int) volume: The new global volume, between 0 and 127 (see above) + */ + void sfx_setVolume(int volume); + + /* Stops all songs currently playing, purges song library + */ + void sfx_all_stop(); + + + /*****************/ + /* Song basics */ + /*****************/ + + /* Adds a song to the internal sound library + ** Parameters: (SongIterator *) it: The iterator describing the song + ** (int) priority: Initial song priority (higher <-> more important) + ** (SongHandle) handle: The handle to associate with the song + */ + void sfx_add_song(SongIterator *it, int priority, SongHandle handle, int resnum); + + + /* Deletes a song and its associated song iterator from the song queue + ** Parameters: (SongHandle) handle: The song to remove + */ + void sfx_remove_song(SongHandle handle); + + + /**********************/ + /* Song modifications */ + /**********************/ + + + /* Sets the song status, i.e. whether it is playing, suspended, or stopped. + ** Parameters: (SongHandle) handle: Handle of the song to modify + ** (int) status: The song status the song should assume + ** WAITING and PLAYING are set implicitly and essentially describe the same state + ** as far as this function is concerned. + */ + void sfx_song_set_status(SongHandle handle, int status); + + /* Sets the new song priority + ** Parameters: (SongHandle) handle: The handle to modify + ** (int) priority: The priority to set + */ + void sfx_song_renice(SongHandle handle, int priority); + + /* Sets the number of loops for the specified song + ** Parameters: (SongHandle) handle: The song handle to reference + ** (int) loops: Number of loops to set + */ + void sfx_song_set_loops(SongHandle handle, int loops); + + /* Sets the number of loops for the specified song + ** Parameters: (SongHandle) handle: The song handle to reference + ** (int) hold: Number of loops to setn + */ + void sfx_song_set_hold(SongHandle handle, int hold); + + /* Instructs a song to be faded out + ** Parameters: (SongHandle) handle: The song handle to reference + ** (fade_params_t *) fade_setup: The precise fade-out configuration to use + */ + void sfx_song_set_fade(SongHandle handle, fade_params_t *fade_setup); + + + // Previously undocumented: + Common::Error sfx_send_midi(SongHandle handle, int channel, + int command, int arg1, int arg2); + + // misc + + /** + * Determines the polyphony of the player in use. + * @return Number of voices the active player can emit + */ + int sfx_get_player_polyphony(); + + /** + * Tells the player to stop its internal iterator. + */ + void sfx_reset_player(); + + /** + * Pass a raw MIDI event to the synth of the player. + * @param argc Length of buffer holding the midi event + * @param argv The buffer itself + */ + void sfx_player_tell_synth(int buf_nr, byte *buf); + +protected: + void freezeTime(); + void thawTime(); + + bool isPlaying(Song *song); + void setSongStatus(Song *song, int status); + void updateSingleSong(); + void updateMultiSong(); + void update(); +}; + +} // End of namespace Sci + +#endif // USE_OLD_MUSIC_FUNCTIONS + +#endif // SCI_SFX_CORE_H diff --git a/engines/sci/sound/iterator/iterator.cpp b/engines/sci/sound/iterator/iterator.cpp new file mode 100644 index 0000000000..5d9d63e5af --- /dev/null +++ b/engines/sci/sound/iterator/iterator.cpp @@ -0,0 +1,1686 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +/* Song iterators */ + +#include "common/util.h" + +#include "sci/sci.h" +#ifdef USE_OLD_MUSIC_FUNCTIONS + +#include "sci/sound/iterator/iterator_internal.h" +#include "sci/engine/state.h" // for sfx_player_tell_synth :/ +#include "sci/sound/iterator/core.h" // for sfx_player_tell_synth + +#include "sound/audiostream.h" +#include "sound/mixer.h" +#include "sound/decoders/raw.h" + +namespace Sci { + + +static const int MIDI_cmdlen[16] = {0, 0, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 1, 1, 2, 0 + }; + +/*#define DEBUG_DECODING*/ +/*#define DEBUG_VERBOSE*/ + +/** Find first set bit in bits and return its index. Returns 0 if bits is 0. */ +static int sci_ffs(int bits) { + if (!bits) + return 0; + + int retval = 1; + + while (!(bits & 1)) { + retval++; + bits >>= 1; + } + + return retval; +} + +static void print_tabs_id(int nr, songit_id_t id) { + while (nr-- > 0) + fprintf(stderr, "\t"); + + fprintf(stderr, "[%08lx] ", id); +} + +BaseSongIterator::BaseSongIterator(byte *data, uint size, songit_id_t id) + : _data(data, size) { + ID = id; +} + +/************************************/ +/*-- SCI0 iterator implementation --*/ +/************************************/ + +#define SCI0_MIDI_OFFSET 33 +#define SCI0_END_OF_SONG 0xfc /* proprietary MIDI command */ + +#define SCI0_PCM_SAMPLE_RATE_OFFSET 0x0e +#define SCI0_PCM_SIZE_OFFSET 0x20 +#define SCI0_PCM_DATA_OFFSET 0x2c + +#define CHECK_FOR_END_ABSOLUTE(offset) \ + if (offset > _data.size()) { \ + warning("Reached end of song without terminator (%x/%x) at %d", offset, _data.size(), __LINE__); \ + return SI_FINISHED; \ + } + +#define CHECK_FOR_END(offset_augment) \ + if ((channel->offset + (offset_augment)) > channel->end) { \ + channel->state = SI_STATE_FINISHED; \ + warning("Reached end of track %d without terminator (%x+%x/%x) at %d", channel->id, channel->offset, offset_augment, channel->end, __LINE__); \ + return SI_FINISHED; \ + } + + +static int _parse_ticks(byte *data, int *offset_p, int size) { + int ticks = 0; + int tempticks; + int offset = 0; + + do { + tempticks = data[offset++]; + ticks += (tempticks == SCI_MIDI_TIME_EXPANSION_PREFIX) ? + SCI_MIDI_TIME_EXPANSION_LENGTH : tempticks; + } while (tempticks == SCI_MIDI_TIME_EXPANSION_PREFIX + && offset < size); + + if (offset_p) + *offset_p = offset; + + return ticks; +} + + +static int _sci0_get_pcm_data(Sci0SongIterator *self, int *rate, int *xoffset, uint *xsize); + + +#define PARSE_FLAG_LOOPS_UNLIMITED (1 << 0) /* Unlimited # of loops? */ +#define PARSE_FLAG_PARAMETRIC_CUE (1 << 1) /* Assume that cues take an additional "cue value" argument */ +/* This implements a difference between SCI0 and SCI1 cues. */ + +void SongIteratorChannel::init(int id_, int offset_, int end_) { + playmask = PLAYMASK_NONE; /* Disable all channels */ + id = id_; + state = SI_STATE_DELTA_TIME; + loop_timepos = 0; + total_timepos = 0; + timepos_increment = 0; + delay = 0; /* Only used for more than one channel */ + last_cmd = 0xfe; + + offset = loop_offset = initial_offset = offset_; + end = end_; +} + +void SongIteratorChannel::resetSynthChannels() { + byte buf[5]; + + // FIXME: Evil hack + SfxState &sound = g_sci->getEngineState()->_sound; + + for (int i = 0; i < MIDI_CHANNELS; i++) { + if (playmask & (1 << i)) { + buf[0] = 0xe0 | i; /* Pitch bend */ + buf[1] = 0x80; /* Wheel center */ + buf[2] = 0x40; + sound.sfx_player_tell_synth(3, buf); + + buf[0] = 0xb0 | i; // Set control + buf[1] = 0x40; // Hold pedal + buf[2] = 0x00; // Off + sound.sfx_player_tell_synth(3, buf); + /* TODO: Reset other controls? */ + } + } +} + +int BaseSongIterator::parseMidiCommand(byte *buf, int *result, SongIteratorChannel *channel, int flags) { + byte cmd; + int paramsleft; + int midi_op; + int midi_channel; + + channel->state = SI_STATE_DELTA_TIME; + + cmd = _data[channel->offset++]; + + if (!(cmd & 0x80)) { + /* 'Running status' mode */ + channel->offset--; + cmd = channel->last_cmd; + } + + if (cmd == 0xfe) { + warning("song iterator subsystem: Corrupted sound resource detected."); + return SI_FINISHED; + } + + midi_op = cmd >> 4; + midi_channel = cmd & 0xf; + paramsleft = MIDI_cmdlen[midi_op]; + +#if 0 + if (1) { + fprintf(stderr, "[IT]: off=%x, cmd=%02x, takes %d args ", + channel->offset - 1, cmd, paramsleft); + fprintf(stderr, "[%02x %02x <%02x> %02x %02x %02x]\n", + _data[channel->offset-3], + _data[channel->offset-2], + _data[channel->offset-1], + _data[channel->offset], + _data[channel->offset+1], + _data[channel->offset+2]); + } +#endif + + buf[0] = cmd; + + + CHECK_FOR_END(paramsleft); + memcpy(buf + 1, _data.begin() + channel->offset, paramsleft); + *result = 1 + paramsleft; + + channel->offset += paramsleft; + + channel->last_cmd = cmd; + + /* Are we supposed to play this channel? */ + if ( + /* First, exclude "global" properties-- such as cues-- from consideration */ + (midi_op < 0xf + && !(cmd == SCI_MIDI_SET_SIGNAL) + && !(SCI_MIDI_CONTROLLER(cmd) + && buf[1] == SCI_MIDI_CUMULATIVE_CUE)) + + /* Next, check if the channel is allowed */ + && (!((1 << midi_channel) & channel->playmask))) + return /* Execute next command */ + nextCommand(buf, result); + + + if (cmd == SCI_MIDI_EOT) { + /* End of track? */ + channel->resetSynthChannels(); + if (_loops > 1) { + /* If allowed, decrement the number of loops */ + if (!(flags & PARSE_FLAG_LOOPS_UNLIMITED)) + *result = --_loops; + +#ifdef DEBUG_DECODING + fprintf(stderr, "%s L%d: (%p):%d Looping ", __FILE__, __LINE__, this, channel->id); + if (flags & PARSE_FLAG_LOOPS_UNLIMITED) + fprintf(stderr, "(indef.)"); + else + fprintf(stderr, "(%d)", _loops); + fprintf(stderr, " %x -> %x\n", + channel->offset, channel->loop_offset); +#endif + channel->offset = channel->loop_offset; + channel->state = SI_STATE_DELTA_TIME; + channel->total_timepos = channel->loop_timepos; + channel->last_cmd = 0xfe; + debugC(2, kDebugLevelSound, "Looping song iterator %08lx.", ID); + return SI_LOOP; + } else { + channel->state = SI_STATE_FINISHED; + return SI_FINISHED; + } + + } else if (cmd == SCI_MIDI_SET_SIGNAL) { + if (buf[1] == SCI_MIDI_SET_SIGNAL_LOOP) { + channel->loop_offset = channel->offset; + channel->loop_timepos = channel->total_timepos; + + return /* Execute next command */ + nextCommand(buf, result); + } else { + /* Used to be conditional <= 127 */ + *result = buf[1]; /* Absolute cue */ + return SI_ABSOLUTE_CUE; + } + } else if (SCI_MIDI_CONTROLLER(cmd)) { + switch (buf[1]) { + + case SCI_MIDI_CUMULATIVE_CUE: + if (flags & PARSE_FLAG_PARAMETRIC_CUE) + _ccc += buf[2]; + else { /* No parameter to CC */ + _ccc++; + /* channel->offset--; */ + } + *result = _ccc; + return SI_RELATIVE_CUE; + + case SCI_MIDI_RESET_ON_SUSPEND: + _resetflag = buf[2]; + break; + + case SCI_MIDI_SET_POLYPHONY: + _polyphony[midi_channel] = buf[2]; + +#if 0 + { + Sci1SongIterator *self1 = (Sci1SongIterator *)this; + int i; + int voices = 0; + for (i = 0; i < self1->_numChannels; i++) { + voices += _polyphony[i]; + } + + printf("SET_POLYPHONY(%d, %d) for a total of %d voices\n", midi_channel, buf[2], voices); + printf("[iterator] DEBUG: Polyphony = [ "); + for (i = 0; i < self1->_numChannels; i++) + printf("%d ", _polyphony[i]); + printf("]\n"); + printf("[iterator] DEBUG: Importance = [ "); + printf("]\n"); + } +#endif + break; + + case SCI_MIDI_SET_REVERB: + break; + + case SCI_MIDI_CHANNEL_MUTE: + warning("CHANNEL_MUTE(%d, %d)", midi_channel, buf[2]); + break; + + case SCI_MIDI_HOLD: { + // Safe cast: This controller is only used in SCI1 + Sci1SongIterator *self1 = (Sci1SongIterator *)this; + + if (buf[2] == self1->_hold) { + channel->offset = channel->initial_offset; + channel->state = SI_STATE_COMMAND; + channel->total_timepos = 0; + + self1->_numLoopedChannels = self1->_numActiveChannels - 1; + + // FIXME: + // This implementation of hold breaks getting out of the + // limo when visiting the airport near the start of LSL5. + // It seems like all channels should be reset here somehow, + // but not sure how. + // Forcing all channel offsets to 0 seems to fix the hang, + // but somehow slows the exit sequence down to take 20 seconds + // instead of about 3. + + return SI_LOOP; + } + + break; + } + case 0x04: /* UNKNOWN NYI (happens in LSL2 gameshow) */ + case 0x46: /* UNKNOWN NYI (happens in LSL3 binoculars) */ + case 0x61: /* UNKNOWN NYI (special for AdLib? Iceman) */ + case 0x73: /* UNKNOWN NYI (happens in Hoyle) */ + case 0xd1: /* UNKNOWN NYI (happens in KQ4 when riding the unicorn) */ + return /* Execute next command */ + nextCommand(buf, result); + + case 0x01: /* modulation */ + case 0x07: /* volume */ + case 0x0a: /* panpot */ + case 0x0b: /* expression */ + case 0x40: /* hold */ + case 0x79: /* reset all */ + /* No special treatment neccessary */ + break; + + } + return 0; + + } else { +#if 0 + /* Perform remapping, if neccessary */ + if (cmd != SCI_MIDI_SET_SIGNAL + && cmd < 0xf0) { /* Not a generic command */ + int chan = cmd & 0xf; + int op = cmd & 0xf0; + + chan = channel_remap[chan]; + buf[0] = chan | op; + } +#endif + + /* Process as normal MIDI operation */ + return 0; + } +} + +int BaseSongIterator::processMidi(byte *buf, int *result, + SongIteratorChannel *channel, int flags) { + CHECK_FOR_END(0); + + switch (channel->state) { + + case SI_STATE_PCM: { + if (_data[channel->offset] == 0 + && _data[channel->offset + 1] == SCI_MIDI_EOT) + /* Fake one extra tick to trick the interpreter into not killing the song iterator right away */ + channel->state = SI_STATE_PCM_MAGIC_DELTA; + else + channel->state = SI_STATE_DELTA_TIME; + return SI_PCM; + } + + case SI_STATE_PCM_MAGIC_DELTA: { + int rate; + int offset; + uint size; + int delay; + if (_sci0_get_pcm_data((Sci0SongIterator *)this, &rate, &offset, &size)) + return SI_FINISHED; /* 'tis broken */ + channel->state = SI_STATE_FINISHED; + delay = (size * 50 + rate - 1) / rate; /* number of ticks to completion*/ + + debugC(2, kDebugLevelSound, "delaying %d ticks", delay); + return delay; + } + + case SI_STATE_UNINITIALISED: + warning("Attempt to read command from uninitialized iterator"); + init(); + return nextCommand(buf, result); + + case SI_STATE_FINISHED: + return SI_FINISHED; + + case SI_STATE_DELTA_TIME: { + int offset; + int ticks = _parse_ticks(_data.begin() + channel->offset, + &offset, + _data.size() - channel->offset); + + channel->offset += offset; + channel->delay += ticks; + channel->timepos_increment = ticks; + + CHECK_FOR_END(0); + + channel->state = SI_STATE_COMMAND; + + if (ticks) + return ticks; + } + + /* continute otherwise... */ + + case SI_STATE_COMMAND: { + int retval; + channel->total_timepos += channel->timepos_increment; + channel->timepos_increment = 0; + + retval = parseMidiCommand(buf, result, channel, flags); + + if (retval == SI_FINISHED) { + if (_numActiveChannels) + --(_numActiveChannels); +#ifdef DEBUG_DECODING + fprintf(stderr, "%s L%d: (%p):%d Finished channel, %d channels left\n", + __FILE__, __LINE__, this, channel->id, + _numActiveChannels); +#endif + /* If we still have channels left... */ + if (_numActiveChannels) { + return nextCommand(buf, result); + } + + /* Otherwise, we have reached the end */ + _loops = 0; + } + + return retval; + } + + default: + error("Invalid iterator state %d", channel->state); + return SI_FINISHED; + } +} + +int Sci0SongIterator::nextCommand(byte *buf, int *result) { + return processMidi(buf, result, &_channel, PARSE_FLAG_PARAMETRIC_CUE); +} + +static int _sci0_header_magic_p(byte *data, int offset, int size) { + if (offset + 0x10 > size) + return 0; + return (data[offset] == 0x1a) + && (data[offset + 1] == 0x00) + && (data[offset + 2] == 0x01) + && (data[offset + 3] == 0x00); +} + + +static int _sci0_get_pcm_data(Sci0SongIterator *self, + int *rate, int *xoffset, uint *xsize) { + int tries = 2; + bool found_it = false; + byte *pcm_data; + int size; + uint offset = SCI0_MIDI_OFFSET; + + if (self->_data[0] != 2) + return 1; + /* No such luck */ + + while ((tries--) && (offset < self->_data.size()) && (!found_it)) { + // Search through the garbage manually + // FIXME: Replace offset by an iterator + Common::Array<byte>::iterator iter = Common::find(self->_data.begin() + offset, self->_data.end(), SCI0_END_OF_SONG); + + if (iter == self->_data.end()) { + warning("Playing unterminated song"); + return 1; + } + + // add one to move it past the END_OF_SONG marker + iter++; + offset = iter - self->_data.begin(); // FIXME + + + if (_sci0_header_magic_p(self->_data.begin(), offset, self->_data.size())) + found_it = true; + } + + if (!found_it) { + warning("Song indicates presence of PCM, but" + " none found (finally at offset %04x)", offset); + + return 1; + } + + pcm_data = self->_data.begin() + offset; + + size = READ_LE_UINT16(pcm_data + SCI0_PCM_SIZE_OFFSET); + + /* Two of the format parameters are fixed by design: */ + *rate = READ_LE_UINT16(pcm_data + SCI0_PCM_SAMPLE_RATE_OFFSET); + + if (offset + SCI0_PCM_DATA_OFFSET + size != self->_data.size()) { + int d = offset + SCI0_PCM_DATA_OFFSET + size - self->_data.size(); + + warning("PCM advertizes %d bytes of data, but %d" + " bytes are trailing in the resource", + size, self->_data.size() - (offset + SCI0_PCM_DATA_OFFSET)); + + if (d > 0) + size -= d; /* Fix this */ + } + + *xoffset = offset; + *xsize = size; + + return 0; +} + +static Audio::AudioStream *makeStream(byte *data, int size, int rate) { + debugC(2, kDebugLevelSound, "Playing PCM data of size %d, rate %d", size, rate); + + // Duplicate the data + byte *sound = (byte *)malloc(size); + memcpy(sound, data, size); + + // Convert stream format flags + int flags = Audio::FLAG_UNSIGNED; + return Audio::makeRawStream(sound, size, rate, flags); +} + +Audio::AudioStream *Sci0SongIterator::getAudioStream() { + int rate; + int offset; + uint size; + if (_sci0_get_pcm_data(this, &rate, &offset, &size)) + return NULL; + + _channel.state = SI_STATE_FINISHED; /* Don't play both PCM and music */ + + return makeStream(_data.begin() + offset + SCI0_PCM_DATA_OFFSET, size, rate); +} + +SongIterator *Sci0SongIterator::handleMessage(Message msg) { + if (msg._class == _SIMSG_BASE) { + switch (msg._type) { + + case _SIMSG_BASEMSG_PRINT: + print_tabs_id(msg._arg.i, ID); + debugC(2, kDebugLevelSound, "SCI0: dev=%d, active-chan=%d, size=%d, loops=%d", + _deviceId, _numActiveChannels, _data.size(), _loops); + break; + + case _SIMSG_BASEMSG_SET_LOOPS: + _loops = msg._arg.i; + break; + + case _SIMSG_BASEMSG_STOP: { + songit_id_t sought_id = msg.ID; + + if (sought_id == ID) + _channel.state = SI_STATE_FINISHED; + break; + } + + case _SIMSG_BASEMSG_SET_PLAYMASK: { + int i; + _deviceId = msg._arg.i; + + /* Set all but the rhytm channel mask bits */ + _channel.playmask &= ~(1 << MIDI_RHYTHM_CHANNEL); + + for (i = 0; i < MIDI_CHANNELS; i++) + if (_data[2 + (i << 1)] & _deviceId + && i != MIDI_RHYTHM_CHANNEL) + _channel.playmask |= (1 << i); + } + break; + + case _SIMSG_BASEMSG_SET_RHYTHM: + _channel.playmask &= ~(1 << MIDI_RHYTHM_CHANNEL); + if (msg._arg.i) + _channel.playmask |= (1 << MIDI_RHYTHM_CHANNEL); + break; + + case _SIMSG_BASEMSG_SET_FADE: { + fade_params_t *fp = (fade_params_t *) msg._arg.p; + fade.action = fp->action; + fade.final_volume = fp->final_volume; + fade.ticks_per_step = fp->ticks_per_step; + fade.step_size = fp->step_size; + break; + } + + default: + return NULL; + } + + return this; + } + return NULL; +} + +int Sci0SongIterator::getTimepos() { + return _channel.total_timepos; +} + +Sci0SongIterator::Sci0SongIterator(byte *data, uint size, songit_id_t id) + : BaseSongIterator(data, size, id) { + channel_mask = 0xffff; // Allocate all channels by default + _channel.state = SI_STATE_UNINITIALISED; + + for (int i = 0; i < MIDI_CHANNELS; i++) + _polyphony[i] = data[1 + (i << 1)]; + + init(); +} + +void Sci0SongIterator::init() { + fade.action = FADE_ACTION_NONE; + _resetflag = 0; + _loops = 0; + priority = 0; + + _ccc = 0; /* Reset cumulative cue counter */ + _numActiveChannels = 1; + _channel.init(0, SCI0_MIDI_OFFSET, _data.size()); + _channel.resetSynthChannels(); + + if (_data[0] == 2) /* Do we have an embedded PCM? */ + _channel.state = SI_STATE_PCM; +} + +SongIterator *Sci0SongIterator::clone(int delta) { + Sci0SongIterator *newit = new Sci0SongIterator(*this); + return newit; +} + + +/***************************/ +/*-- SCI1 song iterators --*/ +/***************************/ + +int Sci1SongIterator::initSample(const int offset) { + Sci1Sample sample; + int rate; + int length; + int begin; + int end; + + CHECK_FOR_END_ABSOLUTE((uint)offset + 10); + if (_data[offset + 1] != 0) + warning("[iterator-1] In sample at offset 0x04x: Byte #1 is %02x instead of zero", + _data[offset + 1]); + + rate = (int16)READ_LE_UINT16(_data.begin() + offset + 2); + length = READ_LE_UINT16(_data.begin() + offset + 4); + begin = (int16)READ_LE_UINT16(_data.begin() + offset + 6); + end = (int16)READ_LE_UINT16(_data.begin() + offset + 8); + + CHECK_FOR_END_ABSOLUTE((uint)(offset + 10 + length)); + + sample.delta = begin; + sample.size = length; + sample._data = _data.begin() + offset + 10; + +#ifdef DEBUG_VERBOSE + fprintf(stderr, "[SAMPLE] %x/%x/%x/%x l=%x\n", + offset + 10, begin, end, _data.size(), length); +#endif + + sample.rate = rate; + + sample.announced = false; + + /* Insert into the sample list at the right spot, keeping it sorted by delta */ + Common::List<Sci1Sample>::iterator seeker = _samples.begin(); + while (seeker != _samples.end() && seeker->delta < begin) + ++seeker; + _samples.insert(seeker, sample); + + return 0; /* Everything's fine */ +} + +int Sci1SongIterator::initSong() { + int last_time; + uint offset = 0; + _numChannels = 0; + _samples.clear(); +// _deviceId = 0x0c; + + if (_data[offset] == 0xf0) { + priority = _data[offset + 1]; + + offset += 8; + } + + while (_data[offset] != 0xff + && _data[offset] != _deviceId) { + offset++; + CHECK_FOR_END_ABSOLUTE(offset + 1); + while (_data[offset] != 0xff) { + CHECK_FOR_END_ABSOLUTE(offset + 7); + offset += 6; + } + offset++; + } + + if (_data[offset] == 0xff) { + warning("[iterator] Song does not support hardware 0x%02x", _deviceId); + return 1; + } + + offset++; + + while (_data[offset] != 0xff) { /* End of list? */ + uint track_offset; + int end; + offset += 2; + + CHECK_FOR_END_ABSOLUTE(offset + 4); + + track_offset = READ_LE_UINT16(_data.begin() + offset); + end = READ_LE_UINT16(_data.begin() + offset + 2); + + CHECK_FOR_END_ABSOLUTE(track_offset - 1); + + if (_data[track_offset] == 0xfe) { + if (initSample(track_offset)) + return 1; /* Error */ + } else { + /* Regular MIDI channel */ + if (_numChannels >= MIDI_CHANNELS) { + warning("[iterator] Song has more than %d channels, cutting them off", + MIDI_CHANNELS); + break; /* Scan for remaining samples */ + } else { + int channel_nr = _data[track_offset] & 0xf; + SongIteratorChannel &channel = _channels[_numChannels++]; + + /* + if (_data[track_offset] & 0xf0) + printf("Channel %d has mapping bits %02x\n", + channel_nr, _data[track_offset] & 0xf0); + */ + + // Add 2 to skip over header bytes */ + channel.init(channel_nr, track_offset + 2, track_offset + end); + channel.resetSynthChannels(); + + _polyphony[_numChannels - 1] = _data[channel.offset - 1] & 15; + + channel.playmask = ~0; /* Enable all */ + channel_mask |= (1 << channel_nr); + + CHECK_FOR_END_ABSOLUTE(offset + end); + } + } + offset += 4; + CHECK_FOR_END_ABSOLUTE(offset); + } + + /* Now ensure that sample deltas are relative to the previous sample */ + last_time = 0; + _numActiveChannels = _numChannels; + _numLoopedChannels = 0; + + for (Common::List<Sci1Sample>::iterator seeker = _samples.begin(); + seeker != _samples.end(); ++seeker) { + int prev_last_time = last_time; + //printf("[iterator] Detected sample: %d Hz, %d bytes at time %d\n", + // seeker->format.rate, seeker->size, seeker->delta); + last_time = seeker->delta; + seeker->delta -= prev_last_time; + } + + return 0; /* Success */ +} + +int Sci1SongIterator::getSmallestDelta() const { + int d = -1; + for (int i = 0; i < _numChannels; i++) + if (_channels[i].state == SI_STATE_COMMAND + && (d == -1 || _channels[i].delay < d)) + d = _channels[i].delay; + + if (!_samples.empty() && _samples.begin()->delta < d) + return _samples.begin()->delta; + else + return d; +} + +void Sci1SongIterator::updateDelta(int delta) { + if (!_samples.empty()) + _samples.begin()->delta -= delta; + + for (int i = 0; i < _numChannels; i++) + if (_channels[i].state == SI_STATE_COMMAND) + _channels[i].delay -= delta; +} + +bool Sci1SongIterator::noDeltaTime() const { + for (int i = 0; i < _numChannels; i++) + if (_channels[i].state == SI_STATE_DELTA_TIME) + return false; + return true; +} + +#define COMMAND_INDEX_NONE -1 +#define COMMAND_INDEX_PCM -2 + +int Sci1SongIterator::getCommandIndex() const { + /* Determine the channel # of the next active event, or -1 */ + int i; + int base_delay = 0x7ffffff; + int best_chan = COMMAND_INDEX_NONE; + + for (i = 0; i < _numChannels; i++) + if ((_channels[i].state != SI_STATE_PENDING) + && (_channels[i].state != SI_STATE_FINISHED)) { + + if ((_channels[i].state == SI_STATE_DELTA_TIME) + && (_channels[i].delay == 0)) + return i; + /* First, read all unknown delta times */ + + if (_channels[i].delay < base_delay) { + best_chan = i; + base_delay = _channels[i].delay; + } + } + + if (!_samples.empty() && base_delay >= _samples.begin()->delta) + return COMMAND_INDEX_PCM; + + return best_chan; +} + + +Audio::AudioStream *Sci1SongIterator::getAudioStream() { + Common::List<Sci1Sample>::iterator sample = _samples.begin(); + if (sample != _samples.end() && sample->delta <= 0) { + Audio::AudioStream *feed = makeStream(sample->_data, sample->size, sample->rate); + _samples.erase(sample); + + return feed; + } else + return NULL; +} + +int Sci1SongIterator::nextCommand(byte *buf, int *result) { + + if (!_initialised) { + //printf("[iterator] DEBUG: Initialising for %d\n", _deviceId); + _initialised = true; + if (initSong()) + return SI_FINISHED; + } + + + if (_delayRemaining) { + int delay = _delayRemaining; + _delayRemaining = 0; + return delay; + } + + int retval = 0; + do { /* All delays must be processed separately */ + int chan = getCommandIndex(); + + if (chan == COMMAND_INDEX_NONE) { + return SI_FINISHED; + } + + if (chan == COMMAND_INDEX_PCM) { + + if (_samples.begin()->announced) { + /* Already announced; let's discard it */ + Audio::AudioStream *feed = getAudioStream(); + delete feed; + } else { + int delay = _samples.begin()->delta; + + if (delay) { + updateDelta(delay); + return delay; + } + /* otherwise we're touching a PCM */ + _samples.begin()->announced = true; + return SI_PCM; + } + } else { /* Not a PCM */ + + retval = processMidi(buf, result, + &(_channels[chan]), + PARSE_FLAG_LOOPS_UNLIMITED); + + if (retval == SI_LOOP) { + _numLoopedChannels++; + _channels[chan].state = SI_STATE_PENDING; + _channels[chan].delay = 0; + + if (_numLoopedChannels == _numActiveChannels) { + int i; + + /* Everyone's ready: Let's loop */ + for (i = 0; i < _numChannels; i++) + if (_channels[i].state == SI_STATE_PENDING) + _channels[i].state = SI_STATE_DELTA_TIME; + + _numLoopedChannels = 0; + return SI_LOOP; + } + } else if (retval == SI_FINISHED) { +#ifdef DEBUG + fprintf(stderr, "FINISHED some channel\n"); +#endif + } else if (retval > 0) { + int sd ; + sd = getSmallestDelta(); + + if (noDeltaTime() && sd) { + /* No other channel is ready */ + updateDelta(sd); + + /* Only from here do we return delta times */ + return sd; + } + } + + } /* Not a PCM */ + + } while (retval > 0); + + return retval; +} + +SongIterator *Sci1SongIterator::handleMessage(Message msg) { + if (msg._class == _SIMSG_BASE) { /* May extend this in the future */ + switch (msg._type) { + + case _SIMSG_BASEMSG_PRINT: { + int playmask = 0; + int i; + + for (i = 0; i < _numChannels; i++) + playmask |= _channels[i].playmask; + + print_tabs_id(msg._arg.i, ID); + debugC(2, kDebugLevelSound, "SCI1: chan-nr=%d, playmask=%04x", + _numChannels, playmask); + } + break; + + case _SIMSG_BASEMSG_STOP: { + songit_id_t sought_id = msg.ID; + int i; + + if (sought_id == ID) { + ID = 0; + + for (i = 0; i < _numChannels; i++) + _channels[i].state = SI_STATE_FINISHED; + } + break; + } + + case _SIMSG_BASEMSG_SET_PLAYMASK: + if (msg.ID == ID) { + channel_mask = 0; + + _deviceId = msg._arg.i; + + if (_initialised) { + int i; + int toffset = -1; + + for (i = 0; i < _numChannels; i++) + if (_channels[i].state != SI_STATE_FINISHED + && _channels[i].total_timepos > toffset) { + toffset = _channels[i].total_timepos + + _channels[i].timepos_increment + - _channels[i].delay; + } + + /* Find an active channel so that we can + ** get the correct time offset */ + + initSong(); + + toffset -= _delayRemaining; + _delayRemaining = 0; + + if (toffset > 0) + return new_fast_forward_iterator(this, toffset); + } else { + initSong(); + _initialised = true; + } + + break; + + } + + case _SIMSG_BASEMSG_SET_LOOPS: + if (msg.ID == ID) + _loops = (msg._arg.i > 32767) ? 99 : 0; + /* 99 is arbitrary, but we can't use '1' because of + ** the way we're testing in the decoding section. */ + break; + + case _SIMSG_BASEMSG_SET_HOLD: + _hold = msg._arg.i; + break; + case _SIMSG_BASEMSG_SET_RHYTHM: + /* Ignore */ + break; + + case _SIMSG_BASEMSG_SET_FADE: { + fade_params_t *fp = (fade_params_t *) msg._arg.p; + fade.action = fp->action; + fade.final_volume = fp->final_volume; + fade.ticks_per_step = fp->ticks_per_step; + fade.step_size = fp->step_size; + break; + } + + default: + warning("Unsupported command %d to SCI1 iterator", msg._type); + } + return this; + } + return NULL; +} + +Sci1SongIterator::Sci1SongIterator(byte *data, uint size, songit_id_t id) + : BaseSongIterator(data, size, id) { + channel_mask = 0; // Defer channel allocation + + for (int i = 0; i < MIDI_CHANNELS; i++) + _polyphony[i] = 0; // Unknown + + init(); +} + +void Sci1SongIterator::init() { + fade.action = FADE_ACTION_NONE; + _resetflag = 0; + _loops = 0; + priority = 0; + + _ccc = 0; + _deviceId = 0x00; // Default to Sound Blaster/AdLib for purposes of cue computation + _numChannels = 0; + _initialised = false; + _delayRemaining = 0; + _loops = 0; + _hold = 0; + memset(_polyphony, 0, sizeof(_polyphony)); +} + +Sci1SongIterator::~Sci1SongIterator() { +} + + +SongIterator *Sci1SongIterator::clone(int delta) { + Sci1SongIterator *newit = new Sci1SongIterator(*this); + newit->_delayRemaining = delta; + return newit; +} + +int Sci1SongIterator::getTimepos() { + int max = 0; + int i; + + for (i = 0; i < _numChannels; i++) + if (_channels[i].total_timepos > max) + max = _channels[i].total_timepos; + + return max; +} + +/** + * A song iterator with the purpose of sending notes-off channel commands. + */ +class CleanupSongIterator : public SongIterator { +public: + CleanupSongIterator(uint channels) { + channel_mask = channels; + ID = 17; + } + + int nextCommand(byte *buf, int *result); + Audio::AudioStream *getAudioStream() { return NULL; } + SongIterator *handleMessage(Message msg); + int getTimepos() { return 0; } + SongIterator *clone(int delta) { return new CleanupSongIterator(*this); } +}; + +SongIterator *CleanupSongIterator::handleMessage(Message msg) { + if (msg._class == _SIMSG_BASEMSG_PRINT && msg._type == _SIMSG_BASEMSG_PRINT) { + print_tabs_id(msg._arg.i, ID); + debugC(2, kDebugLevelSound, "CLEANUP"); + } + + return NULL; +} + +int CleanupSongIterator::nextCommand(byte *buf, int *result) { + /* Task: Return channel-notes-off for each channel */ + if (channel_mask) { + int bs = sci_ffs(channel_mask) - 1; + + channel_mask &= ~(1 << bs); + buf[0] = 0xb0 | bs; /* Controller */ + buf[1] = SCI_MIDI_CHANNEL_NOTES_OFF; + buf[2] = 0; /* Hmm... */ + *result = 3; + return 0; + } else + return SI_FINISHED; +} + +/**********************/ +/*-- Timer iterator --*/ +/**********************/ +int TimerSongIterator::nextCommand(byte *buf, int *result) { + if (_delta) { + int d = _delta; + _delta = 0; + return d; + } + return SI_FINISHED; +} + +SongIterator *new_timer_iterator(int delta) { + return new TimerSongIterator(delta); +} + +/**********************************/ +/*-- Fast-forward song iterator --*/ +/**********************************/ + +int FastForwardSongIterator::nextCommand(byte *buf, int *result) { + if (_delta <= 0) + return SI_MORPH; /* Did our duty */ + + while (1) { + int rv = _delegate->nextCommand(buf, result); + + if (rv > 0) { + /* Subtract from the delta we want to wait */ + _delta -= rv; + + /* Done */ + if (_delta < 0) + return -_delta; + } + + if (rv <= 0) + return rv; + } +} + +Audio::AudioStream *FastForwardSongIterator::getAudioStream() { + return _delegate->getAudioStream(); +} + +SongIterator *FastForwardSongIterator::handleMessage(Message msg) { + if (msg._class == _SIMSG_PLASTICWRAP) { + assert(msg._type == _SIMSG_PLASTICWRAP_ACK_MORPH); + + if (_delta <= 0) { + SongIterator *it = _delegate; + delete this; + return it; + } + + warning("[ff-iterator] Morphing without need"); + return this; + } + + if (msg._class == _SIMSG_BASE && msg._type == _SIMSG_BASEMSG_PRINT) { + print_tabs_id(msg._arg.i, ID); + debugC(2, kDebugLevelSound, "FASTFORWARD:"); + msg._arg.i++; + } + + // And continue with the delegate + songit_handle_message(&_delegate, msg); + + return NULL; +} + + +int FastForwardSongIterator::getTimepos() { + return _delegate->getTimepos(); +} + +FastForwardSongIterator::FastForwardSongIterator(SongIterator *capsit, int delta) + : _delegate(capsit), _delta(delta) { + + channel_mask = capsit->channel_mask; +} + +SongIterator *FastForwardSongIterator::clone(int delta) { + FastForwardSongIterator *newit = new FastForwardSongIterator(*this); + newit->_delegate = _delegate->clone(delta); + return newit; +} + +SongIterator *new_fast_forward_iterator(SongIterator *capsit, int delta) { + if (capsit == NULL) + return NULL; + + FastForwardSongIterator *it = new FastForwardSongIterator(capsit, delta); + return it; +} + + +/********************/ +/*-- Tee iterator --*/ +/********************/ + + +static void song_iterator_add_death_listener(SongIterator *it, TeeSongIterator *client) { + for (int i = 0; i < SONGIT_MAX_LISTENERS; ++i) { + if (it->_deathListeners[i] == 0) { + it->_deathListeners[i] = client; + return; + } + } + error("FATAL: Too many death listeners for song iterator"); +} + +static void song_iterator_remove_death_listener(SongIterator *it, TeeSongIterator *client) { + for (int i = 0; i < SONGIT_MAX_LISTENERS; ++i) { + if (it->_deathListeners[i] == client) { + it->_deathListeners[i] = 0; + return; + } + } +} + +static void song_iterator_transfer_death_listeners(SongIterator *it, SongIterator *it_from) { + for (int i = 0; i < SONGIT_MAX_LISTENERS; ++i) { + if (it_from->_deathListeners[i]) + song_iterator_add_death_listener(it, it_from->_deathListeners[i]); + it_from->_deathListeners[i] = 0; + } +} + +static void songit_tee_death_notification(TeeSongIterator *self, SongIterator *corpse) { + if (corpse == self->_children[TEE_LEFT].it) { + self->_status &= ~TEE_LEFT_ACTIVE; + self->_children[TEE_LEFT].it = NULL; + } else if (corpse == self->_children[TEE_RIGHT].it) { + self->_status &= ~TEE_RIGHT_ACTIVE; + self->_children[TEE_RIGHT].it = NULL; + } else { + error("songit_tee_death_notification() failed: Breakpoint in %s, line %d", __FILE__, __LINE__); + } +} + +TeeSongIterator::TeeSongIterator(SongIterator *left, SongIterator *right) { + int i; + int firstfree = 1; /* First free channel */ + int incomplete_map = 0; + + _readyToMorph = false; + _status = TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE; + + _children[TEE_LEFT].it = left; + _children[TEE_RIGHT].it = right; + + /* Default to lhs channels */ + channel_mask = left->channel_mask; + for (i = 0; i < 16; i++) + if (channel_mask & (1 << i) & right->channel_mask + && (i != MIDI_RHYTHM_CHANNEL) /* Share rhythm */) { /*conflict*/ + while ((firstfree == MIDI_RHYTHM_CHANNEL) + /* Either if it's the rhythm channel or if it's taken */ + || (firstfree < MIDI_CHANNELS + && ((1 << firstfree) & channel_mask))) + ++firstfree; + + if (firstfree == MIDI_CHANNELS) { + incomplete_map = 1; + //warning("[songit-tee <%08lx,%08lx>] Could not remap right channel #%d: Out of channels", + // left->ID, right->ID, i); + } else { + _children[TEE_RIGHT].it->channel_remap[i] = firstfree; + + channel_mask |= (1 << firstfree); + } + } +#ifdef DEBUG_TEE_ITERATOR + if (incomplete_map) { + int c; + fprintf(stderr, "[songit-tee <%08lx,%08lx>] Channels:" + " %04x <- %04x | %04x\n", + left->ID, right->ID, + channel_mask, + left->channel_mask, right->channel_mask); + for (c = 0 ; c < 2; c++) + for (i = 0 ; i < 16; i++) + fprintf(stderr, " map [%d][%d] -> %d\n", + c, i, _children[c].it->channel_remap[i]); + } +#endif + + + song_iterator_add_death_listener(left, this); + song_iterator_add_death_listener(right, this); +} + +TeeSongIterator::~TeeSongIterator() { + // When we die, remove any listeners from our children + if (_children[TEE_LEFT].it) { + song_iterator_remove_death_listener(_children[TEE_LEFT].it, this); + } + + if (_children[TEE_RIGHT].it) { + song_iterator_remove_death_listener(_children[TEE_RIGHT].it, this); + } +} + + +int TeeSongIterator::nextCommand(byte *buf, int *result) { + static const int ready_masks[2] = {TEE_LEFT_READY, TEE_RIGHT_READY}; + static const int active_masks[2] = {TEE_LEFT_ACTIVE, TEE_RIGHT_ACTIVE}; + static const int pcm_masks[2] = {TEE_LEFT_PCM, TEE_RIGHT_PCM}; + int i; + int retid; + +#ifdef DEBUG_TEE_ITERATOR + fprintf(stderr, "[Tee] %02x\n", _status); +#endif + + if (!(_status & (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE))) + /* None is active? */ + return SI_FINISHED; + + if (_readyToMorph) + return SI_MORPH; + + if ((_status & (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE)) + != (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE)) { + /* Not all are is active? */ + int which = 0; +#ifdef DEBUG_TEE_ITERATOR + fprintf(stderr, "\tRequesting transformation...\n"); +#endif + if (_status & TEE_LEFT_ACTIVE) + which = TEE_LEFT; + else if (_status & TEE_RIGHT_ACTIVE) + which = TEE_RIGHT; + memcpy(buf, _children[which].buf, sizeof(buf)); + *result = _children[which].result; + _readyToMorph = true; + return _children[which].retval; + } + + /* First, check for unreported PCMs */ + for (i = TEE_LEFT; i <= TEE_RIGHT; i++) + if ((_status & (ready_masks[i] | pcm_masks[i])) + == (ready_masks[i] | pcm_masks[i])) { + _status &= ~ready_masks[i]; + return SI_PCM; + } + + for (i = TEE_LEFT; i <= TEE_RIGHT; i++) + if (!(_status & ready_masks[i])) { + + /* Buffers aren't ready yet */ + _children[i].retval = + songit_next(&(_children[i].it), + _children[i].buf, + &(_children[i].result), + IT_READER_MASK_ALL + | IT_READER_MAY_FREE + | IT_READER_MAY_CLEAN); + + _status |= ready_masks[i]; +#ifdef DEBUG_TEE_ITERATOR + fprintf(stderr, "\t Must check %d: %d\n", i, _children[i].retval); +#endif + + if (_children[i].retval == SI_ABSOLUTE_CUE || + _children[i].retval == SI_RELATIVE_CUE) + return _children[i].retval; + if (_children[i].retval == SI_FINISHED) { + _status &= ~active_masks[i]; + /* Recurse to complete */ +#ifdef DEBUG_TEE_ITERATOR + fprintf(stderr, "\t Child %d signalled completion, recursing w/ status %02x\n", i, _status); +#endif + return nextCommand(buf, result); + } else if (_children[i].retval == SI_PCM) { + _status |= pcm_masks[i]; + _status &= ~ready_masks[i]; + return SI_PCM; + } + } + + + /* We've already handled PCM, MORPH and FINISHED, CUEs & LOOP remain */ + + retid = TEE_LEFT; + if ((_children[TEE_LEFT].retval > 0) + /* Asked to delay */ + && (_children[TEE_RIGHT].retval <= _children[TEE_LEFT].retval)) + /* Is not delaying or not delaying as much */ + retid = TEE_RIGHT; + +#ifdef DEBUG_TEE_ITERATOR + fprintf(stderr, "\tl:%d / r:%d / chose %d\n", + _children[TEE_LEFT].retval, _children[TEE_RIGHT].retval, retid); +#endif + + /* Adjust delta times */ + if (_children[retid].retval > 0 + && _children[1-retid].retval > 0) { + if (_children[1-retid].retval + == _children[retid].retval) + /* If both _children wait the same amount of time, + ** we have to re-fetch commands from both */ + _status &= ~ready_masks[1-retid]; + else + /* If they don't, we can/must re-use the other + ** child's delay time */ + _children[1-retid].retval + -= _children[retid].retval; + } + + _status &= ~ready_masks[retid]; + memcpy(buf, _children[retid].buf, sizeof(buf)); + *result = _children[retid].result; + + return _children[retid].retval; +} + +Audio::AudioStream *TeeSongIterator::getAudioStream() { + static const int pcm_masks[2] = {TEE_LEFT_PCM, TEE_RIGHT_PCM}; + int i; + + for (i = TEE_LEFT; i <= TEE_RIGHT; i++) + if (_status & pcm_masks[i]) { + _status &= ~pcm_masks[i]; + return _children[i].it->getAudioStream(); + } + + return NULL; // No iterator +} + +SongIterator *TeeSongIterator::handleMessage(Message msg) { + if (msg._class == _SIMSG_PLASTICWRAP) { + assert(msg._type == _SIMSG_PLASTICWRAP_ACK_MORPH); + + SongIterator *old_it; + if (!(_status & (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE))) { + delete this; + return NULL; + } else if (!(_status & TEE_LEFT_ACTIVE)) { + delete _children[TEE_LEFT].it; + _children[TEE_LEFT].it = 0; + old_it = _children[TEE_RIGHT].it; + song_iterator_remove_death_listener(old_it, this); + song_iterator_transfer_death_listeners(old_it, this); + delete this; + return old_it; + } else if (!(_status & TEE_RIGHT_ACTIVE)) { + delete _children[TEE_RIGHT].it; + _children[TEE_RIGHT].it = 0; + old_it = _children[TEE_LEFT].it; + song_iterator_remove_death_listener(old_it, this); + song_iterator_transfer_death_listeners(old_it, this); + delete this; + return old_it; + } + + warning("[tee-iterator] Morphing without need"); + return this; + } + + if (msg._class == _SIMSG_BASE && msg._type == _SIMSG_BASEMSG_PRINT) { + print_tabs_id(msg._arg.i, ID); + debugC(2, kDebugLevelSound, "TEE:"); + msg._arg.i++; + } + + // And continue with the children + if (_children[TEE_LEFT].it) + songit_handle_message(&(_children[TEE_LEFT].it), msg); + if (_children[TEE_RIGHT].it) + songit_handle_message(&(_children[TEE_RIGHT].it), msg); + + return NULL; +} + +void TeeSongIterator::init() { + _status = TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE; + _children[TEE_LEFT].it->init(); + _children[TEE_RIGHT].it->init(); +} + +SongIterator *TeeSongIterator::clone(int delta) { + TeeSongIterator *newit = new TeeSongIterator(*this); + + if (_children[TEE_LEFT].it) + newit->_children[TEE_LEFT].it = _children[TEE_LEFT].it->clone(delta); + if (_children[TEE_RIGHT].it) + newit->_children[TEE_RIGHT].it = _children[TEE_RIGHT].it->clone(delta); + + return newit; +} + + +/*************************************/ +/*-- General purpose functionality --*/ +/*************************************/ + +int songit_next(SongIterator **it, byte *buf, int *result, int mask) { + int retval; + + if (!*it) + return SI_FINISHED; + + do { + retval = (*it)->nextCommand(buf, result); + if (retval == SI_MORPH) { + debugC(2, kDebugLevelSound, " Morphing %p (stored at %p)", (void *)*it, (void *)it); + if (!SIMSG_SEND((*it), SIMSG_ACK_MORPH)) { + error("SI_MORPH failed. Breakpoint in %s, line %d", __FILE__, __LINE__); + } else + debugC(2, kDebugLevelSound, "SI_MORPH successful"); + } + + if (retval == SI_FINISHED) + debugC(2, kDebugLevelSound, "[song-iterator] Song finished. mask = %04x, cm=%04x", + mask, (*it)->channel_mask); + if (retval == SI_FINISHED + && (mask & IT_READER_MAY_CLEAN) + && (*it)->channel_mask) { /* This last test will fail + ** with a terminated + ** cleanup iterator */ + int channel_mask = (*it)->channel_mask; + + SongIterator *old_it = *it; + *it = new CleanupSongIterator(channel_mask); + for(uint i = 0; i < MIDI_CHANNELS; i++) + (*it)->channel_remap[i] = old_it->channel_remap[i]; + song_iterator_transfer_death_listeners(*it, old_it); + if (mask & IT_READER_MAY_FREE) + delete old_it; + retval = -9999; /* Continue */ + } + } while (!( /* Until one of the following holds */ + (retval > 0 && (mask & IT_READER_MASK_DELAY)) + || (retval == 0 && (mask & IT_READER_MASK_MIDI)) + || (retval == SI_LOOP && (mask & IT_READER_MASK_LOOP)) + || (retval == SI_ABSOLUTE_CUE && + (mask & IT_READER_MASK_CUE)) + || (retval == SI_RELATIVE_CUE && + (mask & IT_READER_MASK_CUE)) + || (retval == SI_PCM && (mask & IT_READER_MASK_PCM)) + || (retval == SI_FINISHED) + )); + + if (retval == SI_FINISHED && (mask & IT_READER_MAY_FREE)) { + delete *it; + *it = NULL; + } + + return retval; +} + +SongIterator::SongIterator() { + ID = 0; + channel_mask = 0; + fade.action = FADE_ACTION_NONE; + priority = 0; + memset(_deathListeners, 0, sizeof(_deathListeners)); + + // By default, don't remap + for (uint i = 0; i < 16; i++) + channel_remap[i] = i; +} + +SongIterator::SongIterator(const SongIterator &si) { + ID = si.ID; + channel_mask = si.channel_mask; + fade = si.fade; + priority = si.priority; + memset(_deathListeners, 0, sizeof(_deathListeners)); + + for (uint i = 0; i < 16; i++) + channel_remap[i] = si.channel_remap[i]; +} + + +SongIterator::~SongIterator() { + for (int i = 0; i < SONGIT_MAX_LISTENERS; ++i) + if (_deathListeners[i]) + songit_tee_death_notification(_deathListeners[i], this); +} + +SongIterator *songit_new(byte *data, uint size, SongIteratorType type, songit_id_t id) { + BaseSongIterator *it; + + if (!data || size < 22) { + warning("Attempt to instantiate song iterator for null song data"); + return NULL; + } + + + switch (type) { + case SCI_SONG_ITERATOR_TYPE_SCI0: + it = new Sci0SongIterator(data, size, id); + break; + + case SCI_SONG_ITERATOR_TYPE_SCI1: + it = new Sci1SongIterator(data, size, id); + break; + + default: + /**-- Invalid/unsupported sound resources --**/ + warning("Attempt to instantiate invalid/unknown song iterator type %d", type); + return NULL; + } + + return it; +} + +int songit_handle_message(SongIterator **it_reg_p, SongIterator::Message msg) { + SongIterator *it = *it_reg_p; + SongIterator *newit; + + newit = it->handleMessage(msg); + + if (!newit) + return 0; /* Couldn't handle */ + + *it_reg_p = newit; /* Might have self-morphed */ + return 1; +} + +SongIterator *sfx_iterator_combine(SongIterator *it1, SongIterator *it2) { + if (it1 == NULL) + return it2; + if (it2 == NULL) + return it1; + + /* Both are non-NULL: */ + return new TeeSongIterator(it1, it2); +} + +} // End of namespace Sci + +#endif // USE_OLD_MUSIC_FUNCTIONS diff --git a/engines/sci/sound/iterator/iterator.h b/engines/sci/sound/iterator/iterator.h new file mode 100644 index 0000000000..e5c8f50702 --- /dev/null +++ b/engines/sci/sound/iterator/iterator.h @@ -0,0 +1,326 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +/* Song iterator declarations */ + +#ifndef SCI_SFX_SFX_ITERATOR_H +#define SCI_SFX_SFX_ITERATOR_H + +#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS + +#ifdef USE_OLD_MUSIC_FUNCTIONS +#include "sci/sound/drivers/mididriver.h" + +namespace Audio { + class AudioStream; +} + +namespace Sci { + +enum SongIteratorStatus { + SI_FINISHED = -1, /**< Song finished playing */ + SI_LOOP = -2, /**< Song just looped */ + SI_ABSOLUTE_CUE = -3, /**< Found a song cue (absolute) */ + SI_RELATIVE_CUE = -4, /**< Found a song cue (relative) */ + SI_PCM = -5, /**< Found a PCM */ + SI_IGNORE = -6, /**< This event got edited out by the remapper */ + SI_MORPH = -255 /**< Song iterator requested self-morph. */ +}; + +#define FADE_ACTION_NONE 0 +#define FADE_ACTION_FADE_AND_STOP 1 +#define FADE_ACTION_FADE_AND_CONT 2 + +struct fade_params_t { + int ticks_per_step; + int final_volume; + int step_size; + int action; +}; + +/* Helper defs for messages */ +enum { + _SIMSG_BASE, /* Any base decoder */ + _SIMSG_PLASTICWRAP /* Any "Plastic" (discardable) wrapper decoder */ +}; + +/* Base messages */ +enum { + _SIMSG_BASEMSG_SET_LOOPS, /* Set loops */ + _SIMSG_BASEMSG_SET_PLAYMASK, /* Set the current playmask for filtering */ + _SIMSG_BASEMSG_SET_RHYTHM, /* Activate/deactivate rhythm channel */ + _SIMSG_BASEMSG_ACK_MORPH, /* Acknowledge self-morph */ + _SIMSG_BASEMSG_STOP, /* Stop iterator */ + _SIMSG_BASEMSG_PRINT, /* Print self to stderr, after printing param1 tabs */ + _SIMSG_BASEMSG_SET_HOLD, /* Set value of hold parameter to expect */ + _SIMSG_BASEMSG_SET_FADE /* Set fade parameters */ +}; + +/* "Plastic" (discardable) wrapper messages */ +enum { + _SIMSG_PLASTICWRAP_ACK_MORPH = _SIMSG_BASEMSG_ACK_MORPH /* Acknowledge self-morph */ +}; + +/* Messages */ +#define SIMSG_SET_LOOPS(x) _SIMSG_BASE,_SIMSG_BASEMSG_SET_LOOPS,(x) +#define SIMSG_SET_PLAYMASK(x) _SIMSG_BASE,_SIMSG_BASEMSG_SET_PLAYMASK,(x) +#define SIMSG_SET_RHYTHM(x) _SIMSG_BASE,_SIMSG_BASEMSG_SET_RHYTHM,(x) +#define SIMSG_ACK_MORPH _SIMSG_PLASTICWRAP,_SIMSG_PLASTICWRAP_ACK_MORPH,0 +#define SIMSG_STOP _SIMSG_BASE,_SIMSG_BASEMSG_STOP,0 +#define SIMSG_PRINT(indentation) _SIMSG_BASE,_SIMSG_BASEMSG_PRINT,(indentation) +#define SIMSG_SET_HOLD(x) _SIMSG_BASE,_SIMSG_BASEMSG_SET_HOLD,(x) + +/* Message transmission macro: Takes song reference, message reference */ +#define SIMSG_SEND(o, m) songit_handle_message(&(o), SongIterator::Message((o)->ID, m)) +#define SIMSG_SEND_FADE(o, m) songit_handle_message(&(o), SongIterator::Message((o)->ID, _SIMSG_BASE, _SIMSG_BASEMSG_SET_FADE, m)) + +typedef unsigned long songit_id_t; + + +#define SONGIT_MAX_LISTENERS 2 + +class TeeSongIterator; + +class SongIterator { +public: + struct Message { + songit_id_t ID; + uint _class; /* Type of iterator supposed to receive this */ + uint _type; + union { + uint i; + void *p; + } _arg; + + Message() : ID(0), _class(0xFFFF), _type(0xFFFF) {} + + /** + * Create a song iterator message. + * + * @param id: song ID the message is targeted to + * @param recipient_class: Message recipient class + * @param type message type + * @param a argument + * + * @note You should only use this with the SIMSG_* macros + */ + Message(songit_id_t id, int recipient_class, int type, int a) + : ID(id), _class(recipient_class), _type(type) { + _arg.i = a; + } + + /** + * Create a song iterator message, wherein the first parameter is a pointer. + * + * @param id: song ID the message is targeted to + * @param recipient_class: Message recipient class + * @param type message type + * @param a argument + * + * @note You should only use this with the SIMSG_* macros + */ + Message(songit_id_t id, int recipient_class, int type, void *a) + : ID(id), _class(recipient_class), _type(type) { + _arg.p = a; + } + }; + +public: + songit_id_t ID; + uint16 channel_mask; /* Bitmask of all channels this iterator will use */ + fade_params_t fade; + int priority; + + /* Death listeners */ + /* These are not reset during initialisation */ + TeeSongIterator *_deathListeners[SONGIT_MAX_LISTENERS]; + + /* See songit_* for the constructor and non-virtual member functions */ + + byte channel_remap[MIDI_CHANNELS]; ///< Remapping for channels + +public: + SongIterator(); + SongIterator(const SongIterator &); + virtual ~SongIterator(); + + /** + * Resets/initializes the sound iterator. + */ + virtual void init() {} + + /** + * Reads the next MIDI operation _or_ delta time. + * @param buf The buffer to write to (needs to be able to store at least 4 bytes) + * @param result Number of bytes written to the buffer + * (equals the number of bytes that need to be passed + * to the lower layers) for 0, the cue value for SI_CUE, + * or the number of loops remaining for SI_LOOP. + * @return zero if a MIDI operation was written, SI_FINISHED + * if the song has finished playing, SI_LOOP if looping + * (after updating the loop variable), SI_CUE if we found + * a cue, SI_PCM if a PCM was found, or the number of ticks + * to wait before this function should be called next. + * + * @note If SI_PCM is returned, get_pcm() may be used to retrieve the associated + * PCM, but this must be done before any subsequent calls to next(). + * + * @todo The actual buffer size should either be specified or passed in, so that + * we can detect buffer overruns. + */ + virtual int nextCommand(byte *buf, int *result) = 0; + + /** + Checks for the presence of a pcm sample. + * @return NULL if no PCM data was found, an AudioStream otherwise. + */ + virtual Audio::AudioStream *getAudioStream() = 0; + + /** + * Handles a message to the song iterator. + * @param msg the message to handle + * @return NULL if the message was not understood, + * this if the message could be handled, or a new song iterator + * if the current iterator had to be morphed (but the message could + * still be handled) + * + * @note This function is not supposed to be called directly; use + * songit_handle_message() instead. It should not recurse, since songit_handle_message() + * takes care of that and makes sure that its delegate received the message (and + * was morphed) before self. + */ + virtual SongIterator *handleMessage(Message msg) = 0; + + /** + * Gets the song position to store in a savegame. + */ + virtual int getTimepos() = 0; + + /** + * Clone this song iterator. + * @param delta number of ticks that still need to elapse until the + * next item should be read from the song iterator + */ + virtual SongIterator *clone(int delta) = 0; + + +private: + // Make the assignment operator unreachable, just in case... + SongIterator& operator=(const SongIterator&); +}; + + +/********************************/ +/*-- Song iterator operations --*/ +/********************************/ + +enum SongIteratorType { + SCI_SONG_ITERATOR_TYPE_SCI0 = 0, + SCI_SONG_ITERATOR_TYPE_SCI1 = 1 +}; + +#define IT_READER_MASK_MIDI (1 << 0) +#define IT_READER_MASK_DELAY (1 << 1) +#define IT_READER_MASK_LOOP (1 << 2) +#define IT_READER_MASK_CUE (1 << 3) +#define IT_READER_MASK_PCM (1 << 4) +#define IT_READER_MAY_FREE (1 << 10) /* Free SI_FINISHED iterators */ +#define IT_READER_MAY_CLEAN (1 << 11) +/* MAY_CLEAN: May instantiate cleanup iterators +** (use for players; this closes open channels at the end of a song) */ + +#define IT_READER_MASK_ALL ( IT_READER_MASK_MIDI \ + | IT_READER_MASK_DELAY \ + | IT_READER_MASK_LOOP \ + | IT_READER_MASK_CUE \ + | IT_READER_MASK_PCM ) + +/* Convenience wrapper around it->next +** Parameters: (SongIterator **it) Reference to the iterator to access +** (byte *) buf: The buffer to write to (needs to be able to +** store at least 4 bytes) +** (int) mask: IT_READER_MASK options specifying the events to +** listen for +** Returns : (int) zero if a MIDI operation was written, SI_FINISHED +** if the song has finished playing, SI_LOOP if looping +** (after updating the loop variable), SI_CUE if we found +** a cue, SI_PCM if a PCM was found, or the number of ticks +** to wait before this function should be called next. +** (int) *result: Number of bytes written to the buffer +** (equals the number of bytes that need to be passed +** to the lower layers) for 0, the cue value for SI_CUE, +** or the number of loops remaining for SI_LOOP. +*/ +int songit_next(SongIterator **it, byte *buf, int *result, int mask); + +/* Constructs a new song iterator object +** Parameters: (byte *) data: The song data to iterate over +** (uint) size: Number of bytes in the song +** (int) type: One of the SCI_SONG_ITERATOR_TYPEs +** (songit_id_t) id: An ID for addressing the song iterator +** Returns : (SongIterator *) A newly allocated but uninitialized song +** iterator, or NULL if 'type' was invalid or unsupported +*/ +SongIterator *songit_new(byte *data, uint size, SongIteratorType type, songit_id_t id); + +/* Constructs a new song timer iterator object +** Parameters: (int) delta: The delta after which to fire SI_FINISHED +** Returns : (SongIterator *) A newly allocated but uninitialized song +** iterator +*/ +SongIterator *new_timer_iterator(int delta); + +/* Handles a message to the song iterator +** Parameters: (SongIterator **): A reference to the variable storing the song iterator +** Returns : (int) Non-zero if the message was understood +** The song iterator may polymorph as result of msg, so a writeable reference is required. +*/ +int songit_handle_message(SongIterator **it_reg, SongIterator::Message msg); + + +/* Creates a new song iterator which fast-forwards +** Parameters: (SongIterator *) it: The iterator to wrap +** (int) delta: The number of ticks to skip +** Returns : (SongIterator) A newly created song iterator +** which skips all delta times +** until 'delta' has been used up +*/ +SongIterator *new_fast_forward_iterator(SongIterator *it, int delta); + +/* Combines two song iterators into one +** Parameters: (sfx_iterator_t *) it1: One of the two iterators, or NULL +** (sfx_iterator_t *) it2: The other iterator, or NULL +** Returns : (sfx_iterator_t *) A combined iterator +** If a combined iterator is returned, it will be flagged to be allowed to +** dispose of 'it1' and 'it2', where applicable. This means that this +** call should be used by song players, but not by the core sound system +*/ +SongIterator *sfx_iterator_combine(SongIterator *it1, SongIterator *it2); + +} // End of namespace Sci + +#endif // USE_OLD_MUSIC_FUNCTIONS + +#endif // SCI_SFX_SFX_ITERATOR_H diff --git a/engines/sci/sound/iterator/iterator_internal.h b/engines/sci/sound/iterator/iterator_internal.h new file mode 100644 index 0000000000..5a0f0d3ec9 --- /dev/null +++ b/engines/sci/sound/iterator/iterator_internal.h @@ -0,0 +1,276 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SCI_SFX_SFX_ITERATOR_INTERNAL +#define SCI_SFX_SFX_ITERATOR_INTERNAL + +#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS + +#ifdef USE_OLD_MUSIC_FUNCTIONS +#include "sci/sound/iterator/iterator.h" +#include "sci/sound/drivers/mididriver.h" + +#include "common/array.h" +#include "common/list.h" + +namespace Sci { + +/* Iterator types */ + +enum { + SI_STATE_UNINITIALISED = -1, + SI_STATE_DELTA_TIME = 0, ///< Now at a delta time + SI_STATE_COMMAND = 1, ///< Now at a MIDI operation + SI_STATE_PENDING = 2, ///< Pending for loop + SI_STATE_FINISHED = 3, ///< End of song + SI_STATE_PCM = 4, ///< Should report a PCM next (-> DELTA_TIME) + SI_STATE_PCM_MAGIC_DELTA = 5 ///< Should report a ``magic'' one tick delta time next (goes on to FINISHED) +}; + +struct SongIteratorChannel { + + int state; ///< State of this song iterator channel + int offset; ///< Offset into the data chunk */ + int end; ///< Last allowed byte in track */ + int id; ///< Some channel ID */ + + /** + * Number of ticks before the specified channel is next used, or + * CHANNEL_DELAY_MISSING to indicate that the delay has not yet + * been read. + */ + int delay; + + /* Two additional offsets for recovering: */ + int loop_offset; + int initial_offset; + + int playmask; ///< Active playmask (MIDI channels to play in here) */ + int loop_timepos; ///< Total delay for this channel's loop marker */ + int total_timepos; ///< Number of ticks since the beginning, ignoring loops */ + int timepos_increment; ///< Number of ticks until the next command (to add) */ + + byte last_cmd; ///< Last operation executed, for running status */ + +public: + void init(int id, int offset, int end); + void resetSynthChannels(); +}; + +class BaseSongIterator : public SongIterator { +public: + int _polyphony[MIDI_CHANNELS]; ///< # of simultaneous notes on each + + int _ccc; ///< Cumulative cue counter, for those who need it + byte _resetflag; ///< for 0x4C -- on DoSound StopSound, do we return to start? + int _deviceId; ///< ID of the device we generating events for + int _numActiveChannels; ///< Number of active channels + Common::Array<byte> _data; ///< Song data + + int _loops; ///< Number of loops remaining + +public: + BaseSongIterator(byte *data, uint size, songit_id_t id); + +protected: + int parseMidiCommand(byte *buf, int *result, SongIteratorChannel *channel, int flags); + int processMidi(byte *buf, int *result, SongIteratorChannel *channel, int flags); +}; + +/********************************/ +/*--------- SCI 0 --------------*/ +/********************************/ + +class Sci0SongIterator : public BaseSongIterator { +public: + SongIteratorChannel _channel; + +public: + Sci0SongIterator(byte *data, uint size, songit_id_t id); + + int nextCommand(byte *buf, int *result); + Audio::AudioStream *getAudioStream(); + SongIterator *handleMessage(Message msg); + void init(); + int getTimepos(); + SongIterator *clone(int delta); +}; + + +/********************************/ +/*--------- SCI 1 --------------*/ +/********************************/ + + +struct Sci1Sample { + /** + * Time left-- initially, this is 'Sample point 1'. + * After initialisation, it is 'sample point 1 minus the sample + * point of the previous sample' + */ + int delta; + int size; + bool announced; /* Announced for download (SI_PCM) */ + int rate; + byte *_data; +}; + +class Sci1SongIterator : public BaseSongIterator { +public: + SongIteratorChannel _channels[MIDI_CHANNELS]; + + /* Invariant: Whenever channels[i].delay == CHANNEL_DELAY_MISSING, + ** channel_offset[i] points to a delta time object. */ + + bool _initialised; /**!< Whether the MIDI channel setup has been initialised */ + int _numChannels; /**!< Number of channels actually used */ + Common::List<Sci1Sample> _samples; + int _numLoopedChannels; /**!< Number of channels that are ready to loop */ + + int _delayRemaining; /**!< Number of ticks that haven't been polled yet */ + int _hold; + +public: + Sci1SongIterator(byte *data, uint size, songit_id_t id); + ~Sci1SongIterator(); + + int nextCommand(byte *buf, int *result); + Audio::AudioStream *getAudioStream(); + SongIterator *handleMessage(Message msg); + void init(); + int getTimepos(); + SongIterator *clone(int delta); + +private: + int initSample(const int offset); + int initSong(); + + int getSmallestDelta() const; + + void updateDelta(int delta); + + /** Checks that none of the channels is waiting for its delta to be read */ + bool noDeltaTime() const; + + /** Determine the channel # of the next active event, or -1 */ + int getCommandIndex() const; +}; + +#define PLAYMASK_NONE 0x0 + +/***************************/ +/*--------- Timer ---------*/ +/***************************/ + +/** + * A song iterator which waits a specified time and then fires + * SI_FINISHED. Used by DoSound, where audio resources are played (SCI1) + */ +class TimerSongIterator : public SongIterator { +protected: + int _delta; /**!< Remaining time */ + +public: + TimerSongIterator(int delta) : _delta(delta) {} + + int nextCommand(byte *buf, int *result); + Audio::AudioStream *getAudioStream() { return NULL; } + SongIterator *handleMessage(Message msg) { return NULL; } + int getTimepos() { return 0; } + SongIterator *clone(int delta) { return new TimerSongIterator(*this); } +}; + +/**********************************/ +/*--------- Fast Forward ---------*/ +/**********************************/ + +/** + * A song iterator which fast-forwards another iterator. + * Skips all delta times until a specified 'delta' has been used up. + */ +class FastForwardSongIterator : public SongIterator { +protected: + SongIterator *_delegate; + int _delta; /**!< Remaining time */ + +public: + FastForwardSongIterator(SongIterator *capsit, int delta); + + int nextCommand(byte *buf, int *result); + Audio::AudioStream *getAudioStream(); + SongIterator *handleMessage(Message msg); + int getTimepos(); + SongIterator *clone(int delta); +}; + + +/**********************************/ +/*--------- Tee iterator ---------*/ +/**********************************/ + +enum { + TEE_LEFT = 0, + TEE_RIGHT = 1, + TEE_LEFT_ACTIVE = (1<<0), + TEE_RIGHT_ACTIVE = (1<<1), + TEE_LEFT_READY = (1<<2), /**!< left result is ready */ + TEE_RIGHT_READY = (1<<3), /**!< right result is ready */ + TEE_LEFT_PCM = (1<<4), + TEE_RIGHT_PCM = (1<<5) +}; + +/** + * This iterator combines two iterators, returns the next event available from either. + */ +class TeeSongIterator : public SongIterator { +public: + int _status; + + bool _readyToMorph; /**!< One of TEE_MORPH_* above */ + + struct { + SongIterator *it; + byte buf[4]; + int result; + int retval; + } _children[2]; + +public: + TeeSongIterator(SongIterator *left, SongIterator *right); + ~TeeSongIterator(); + + int nextCommand(byte *buf, int *result); + Audio::AudioStream *getAudioStream(); + SongIterator *handleMessage(Message msg); + void init(); + int getTimepos() { return 0; } + SongIterator *clone(int delta); +}; + +} // End of namespace Sci + +#endif // USE_OLD_MUSIC_FUNCTIONS + +#endif // SCI_SFX_SFX_ITERATOR_INTERNAL diff --git a/engines/sci/sound/iterator/songlib.cpp b/engines/sci/sound/iterator/songlib.cpp new file mode 100644 index 0000000000..8bc2e8f476 --- /dev/null +++ b/engines/sci/sound/iterator/songlib.cpp @@ -0,0 +1,189 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS + +#ifdef USE_OLD_MUSIC_FUNCTIONS +#include "sci/sound/iterator/core.h" +#include "sci/sound/iterator/iterator.h" + +namespace Sci { + +#define debug_stream stderr + +Song::Song() : _wakeupTime(0, SFX_TICKS_PER_SEC) { + _handle = 0; + _resourceNum = 0; + _priority = 0; + _status = SOUND_STATUS_STOPPED; + + _restoreBehavior = RESTORE_BEHAVIOR_CONTINUE; + _restoreTime = 0; + + _loops = 0; + _hold = 0; + + _it = 0; + _delay = 0; + + _next = NULL; + _nextPlaying = NULL; + _nextStopping = NULL; +} + +Song::Song(SongHandle handle, SongIterator *it, int priority) : _wakeupTime(0, SFX_TICKS_PER_SEC) { + _handle = handle; + _resourceNum = 0; + _priority = priority; + _status = SOUND_STATUS_STOPPED; + + _restoreBehavior = RESTORE_BEHAVIOR_CONTINUE; + _restoreTime = 0; + + _loops = 0; + _hold = 0; + + _it = it; + _delay = 0; + + _next = NULL; + _nextPlaying = NULL; + _nextStopping = NULL; +} + +void SongLibrary::addSong(Song *song) { + Song **seeker = NULL; + int pri = song->_priority; + + if (NULL == song) { + warning("addSong(): NULL passed for song"); + return; + } + + seeker = &_lib; + while (*seeker && ((*seeker)->_priority > pri)) + seeker = &((*seeker)->_next); + + song->_next = *seeker; + *seeker = song; +} + +void SongLibrary::freeSounds() { + Song *next = _lib; + while (next) { + Song *song = next; + delete song->_it; + song->_it = NULL; + next = song->_next; + delete song; + } + _lib = NULL; +} + + +Song *SongLibrary::findSong(SongHandle handle) { + Song *seeker = _lib; + + while (seeker) { + if (seeker->_handle == handle) + break; + seeker = seeker->_next; + } + + return seeker; +} + +Song *SongLibrary::findNextActive(Song *other) { + Song *seeker = other ? other->_next : _lib; + + while (seeker) { + if ((seeker->_status == SOUND_STATUS_WAITING) || + (seeker->_status == SOUND_STATUS_PLAYING)) + break; + seeker = seeker->_next; + } + + /* Only return songs that have equal priority */ + if (other && seeker && other->_priority > seeker->_priority) + return NULL; + + return seeker; +} + +Song *SongLibrary::findFirstActive() { + return findNextActive(NULL); +} + +int SongLibrary::removeSong(SongHandle handle) { + int retval; + Song *goner = _lib; + + if (!goner) + return -1; + + if (goner->_handle == handle) + _lib = goner->_next; + + else { + while ((goner->_next) && (goner->_next->_handle != handle)) + goner = goner->_next; + + if (goner->_next) { /* Found him? */ + Song *oldnext = goner->_next; + + goner->_next = goner->_next->_next; + goner = oldnext; + } else return -1; /* No. */ + } + + retval = goner->_status; + + delete goner->_it; + delete goner; + + return retval; +} + +int SongLibrary::countSongs() { + Song *seeker = _lib; + int retval = 0; + + while (seeker) { + retval++; + seeker = seeker->_next; + } + + return retval; +} + +void SongLibrary::setSongRestoreBehavior(SongHandle handle, RESTORE_BEHAVIOR action) { + Song *seeker = findSong(handle); + + seeker->_restoreBehavior = action; +} + +} // End of namespace Sci + +#endif // USE_OLD_MUSIC_FUNCTIONS diff --git a/engines/sci/sound/iterator/songlib.h b/engines/sci/sound/iterator/songlib.h new file mode 100644 index 0000000000..acb704edaa --- /dev/null +++ b/engines/sci/sound/iterator/songlib.h @@ -0,0 +1,171 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +/* Song library */ + +#ifndef SCI_SFX_SFX_SONGLIB_H +#define SCI_SFX_SFX_SONGLIB_H + +#include "common/scummsys.h" +#include "sound/timestamp.h" + +#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS +#ifdef USE_OLD_MUSIC_FUNCTIONS + +namespace Sci { + +class SongIterator; + +#define SOUND_STATUS_STOPPED 0 +#define SOUND_STATUS_PLAYING 1 +#define SOUND_STATUS_SUSPENDED 2 +/* suspended: only if ordered from kernel space */ +#define SOUND_STATUS_WAITING 3 +/* "waiting" means "tagged for playing, but not active right now" */ + +typedef unsigned long SongHandle; + +enum RESTORE_BEHAVIOR { + RESTORE_BEHAVIOR_CONTINUE, /* restart a song when restored from + a saved game */ + RESTORE_BEHAVIOR_RESTART /* continue it from where it was */ +}; + +class Song { +public: + SongHandle _handle; + int _resourceNum; /**<! Resource number */ + int _priority; /**!< Song priority (more important if priority is higher) */ + int _status; /* See above */ + + int _restoreBehavior; + int _restoreTime; + + /* Grabbed from the sound iterator, for save/restore purposes */ + int _loops; + int _hold; + + SongIterator *_it; + int _delay; /**!< Delay before accessing the iterator, in ticks */ + + Audio::Timestamp _wakeupTime; /**!< Timestamp indicating the next MIDI event */ + + Song *_next; /**!< Next song or NULL if this is the last one */ + + /** + * Next playing song. Used by the core song system. + */ + Song *_nextPlaying; + + /** + * Next song pending stopping. Used exclusively by the core song system's + * _update_multi_song() + */ + Song *_nextStopping; + +public: + + Song(); + + /** + * Initializes a new song. + * @param handle the sound handle + * @param it the song + * @param priority the song's priority + * @return a freshly allocated song + */ + Song(SongHandle handle, SongIterator *it, int priority); +}; + + +class SongLibrary { +public: + Song *_lib; + +public: + SongLibrary() : _lib(0) {} + + /** Frees a song library. */ + void freeSounds(); + + /** + * Adds a song to a song library. + * @param song song to add + */ + void addSong(Song *song); + + /** + * Looks up the song with the specified handle. + * @param handle sound handle to look for + * @return the song or NULL if it wasn't found + */ + Song *findSong(SongHandle handle); + + /** + * Finds the first song playing with the highest priority. + * @return the song that should be played next, or NULL if there is none + */ + Song *findFirstActive(); + + /** + * Finds the next song playing with the highest priority. + * + * The functions 'findFirstActive' and 'findNextActive' + * allow to iterate over all songs that satisfy the requirement of + * being 'playable'. + * + * @param song a song previously returned from the song library + * @return the next song to play relative to 'song', or NULL if none are left + */ + Song *findNextActive(Song *song); + + /** + * Removes a song from the library. + * @param handle handle of the song to remove + * @return the status of the song that was removed + */ + int removeSong(SongHandle handle); + + /** + * Counts the number of songs in a song library. + * @return the number of songs + */ + int countSongs(); + + /** + * Determines what should be done with the song "handle" when restoring + * it from a saved game. + * @param handle sound handle being restored + * @param action desired action + */ + void setSongRestoreBehavior(SongHandle handle, + RESTORE_BEHAVIOR action); +}; + +} // End of namespace Sci + +#endif // USE_OLD_MUSIC_FUNCTIONS + +#endif // SCI_SSFX_SFX_SONGLIB_H diff --git a/engines/sci/sound/iterator/test-iterator.cpp b/engines/sci/sound/iterator/test-iterator.cpp new file mode 100644 index 0000000000..0d603a89fd --- /dev/null +++ b/engines/sci/sound/iterator/test-iterator.cpp @@ -0,0 +1,423 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "iterator.h" +#include "iterator_internal.h" +#include <stdarg.h> +#include <stdio.h> + +using namespace Sci; + +#define ASSERT_S(x) if (!(x)) { error("Failed assertion in L%d: " #x, __LINE__); return; } +#define ASSERT(x) ASSERT_S(x) + +/* Tests the song iterators */ + +int errors = 0; + +void error(char *fmt, ...) { + va_list ap; + + fprintf(stderr, "[ERROR] "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + ++errors; +} + + +/* The simple iterator will finish after a fixed amount of time. Before that, +** it emits (absolute) cues in ascending order. */ +struct simple_iterator : public SongIterator { + int lifetime_remaining; + char *cues; + int cue_counter; + int cue_progress; + int cues_nr; +}; + +int simple_it_next(SongIterator *_self, unsigned char *buf, int *result) { + simple_iterator *self = (simple_iterator *)_self; + + if (self->lifetime_remaining == -1) { + error("Song iterator called post mortem"); + return SI_FINISHED; + } + + if (self->lifetime_remaining) { + + if (self->cue_counter < self->cues_nr) { + int time_to_cue = self->cues[self->cue_counter]; + + if (self->cue_progress == time_to_cue) { + ++self->cue_counter; + self->cue_progress = 0; + *result = self->cue_counter; + return SI_ABSOLUTE_CUE; + } else { + int retval = time_to_cue - self->cue_progress; + self->cue_progress = time_to_cue; + + if (retval > self->lifetime_remaining) { + retval = self->lifetime_remaining; + self->lifetime_remaining = 0; + self->cue_progress = retval; + return retval; + } + + self->lifetime_remaining -= retval; + return retval; + } + } else { + int retval = self->lifetime_remaining; + self->lifetime_remaining = 0; + return retval; + } + + } else { + self->lifetime_remaining = -1; + return SI_FINISHED; + } +} + +Audio::AudioStream *simple_it_pcm_feed(SongIterator *_self) { + error("No PCM feed"); + return NULL; +} + +void simple_it_init(SongIterator *_self) { +} + +SongIterator *simple_it_handle_message(SongIterator *_self, SongIterator::Message msg) { + return NULL; +} + +void simple_it_cleanup(SongIterator *_self) { +} + +/* Initialises the simple iterator. +** Parameters: (int) delay: Number of ticks until the iterator finishes +** (int *) cues: An array of cue delays (cue values are [1,2...]) +** (int) cues_nr: Number of cues in ``cues'' +** The first cue is emitted after cues[0] ticks, and it is 1. After cues[1] additional ticks +** the next cue is emitted, and so on. */ +SongIterator *setup_simple_iterator(int delay, char *cues, int cues_nr) { + simple_iterator.lifetime_remaining = delay; + simple_iterator.cues = cues; + simple_iterator.cue_counter = 0; + simple_iterator.cues_nr = cues_nr; + simple_iterator.cue_progress = 0; + + simple_iterator.ID = 42; + simple_iterator.channel_mask = 0x004f; + simple_iterator.flags = 0; + simple_iterator.priority = 1; + + simple_iterator.death_listeners_nr = 0; + + simple_iterator.cleanup = simple_it_cleanup; + simple_iterator.init = simple_it_init; + simple_iterator.handle_message = simple_it_handle_message; + simple_iterator.get_pcm_feed = simple_it_pcm_feed; + simple_iterator.next = simple_it_next; + + return (SongIterator *) &simple_iterator; +} + +#define ASSERT_SIT ASSERT(it == simple_it) +#define ASSERT_FFIT ASSERT(it == ff_it) +#define ASSERT_NEXT(n) ASSERT(songit_next(&it, data, &result, IT_READER_MASK_ALL) == n) +#define ASSERT_RESULT(n) ASSERT(result == n) +#define ASSERT_CUE(n) ASSERT_NEXT(SI_ABSOLUTE_CUE); ASSERT_RESULT(n) + +void test_simple_it() { + SongIterator *it; + SongIterator *simple_it = (SongIterator *) & simple_iterator; + unsigned char data[4]; + int result; + puts("[TEST] simple iterator (test artifact)"); + + it = setup_simple_iterator(42, NULL, 0); + + ASSERT_SIT; + ASSERT_NEXT(42); + ASSERT_SIT; + ASSERT_NEXT(SI_FINISHED); + ASSERT_SIT; + + it = setup_simple_iterator(42, "\003\004", 2); + ASSERT_SIT; + ASSERT_NEXT(3); + ASSERT_CUE(1); + ASSERT_SIT; + ASSERT_NEXT(4); + ASSERT_CUE(2); + ASSERT_SIT; +// warning("XXX => %d", songit_next(&it, data, &result, IT_READER_MASK_ALL)); + ASSERT_NEXT(35); + ASSERT_NEXT(SI_FINISHED); + ASSERT_SIT; + + puts("[TEST] Test OK."); +} + +void test_fastforward() { + SongIterator *it; + SongIterator *simple_it = (SongIterator *) & simple_iterator; + SongIterator *ff_it; + unsigned char data[4]; + int result; + puts("[TEST] fast-forward iterator"); + + it = setup_simple_iterator(42, NULL, 0); + ff_it = it = new_fast_forward_iterator(it, 0); + ASSERT_FFIT; + ASSERT_NEXT(42); + ASSERT_SIT; /* Must have morphed back */ + ASSERT_NEXT(SI_FINISHED); + ASSERT_SIT; + + it = setup_simple_iterator(42, NULL, 0); + ff_it = it = new_fast_forward_iterator(it, 1); + ASSERT_FFIT; + ASSERT_NEXT(41); + /* May or may not have morphed back here */ + ASSERT_NEXT(SI_FINISHED); + ASSERT_SIT; + + it = setup_simple_iterator(42, NULL, 0); + ff_it = it = new_fast_forward_iterator(it, 41); + ASSERT_FFIT; + ASSERT_NEXT(1); + /* May or may not have morphed back here */ + ASSERT_NEXT(SI_FINISHED); + ASSERT_SIT; + + it = setup_simple_iterator(42, NULL, 0); + ff_it = it = new_fast_forward_iterator(it, 42); + ASSERT_NEXT(SI_FINISHED); + /* May or may not have morphed back here */ + + it = setup_simple_iterator(42, NULL, 0); + ff_it = it = new_fast_forward_iterator(it, 10000); + ASSERT_NEXT(SI_FINISHED); + /* May or may not have morphed back here */ + + it = setup_simple_iterator(42, "\003\004", 2); + ff_it = it = new_fast_forward_iterator(it, 2); + ASSERT_FFIT; + ASSERT_NEXT(1); + ASSERT_CUE(1); + ASSERT_SIT; + ASSERT_NEXT(4); + ASSERT_CUE(2); + ASSERT_SIT; + ASSERT_NEXT(35); + ASSERT_NEXT(SI_FINISHED); + ASSERT_SIT; + + it = setup_simple_iterator(42, "\003\004", 2); + ff_it = it = new_fast_forward_iterator(it, 5); + ASSERT_FFIT; + ASSERT_CUE(1); + ASSERT_FFIT; + ASSERT_NEXT(2); + ASSERT_CUE(2); + ASSERT_SIT; + ASSERT_NEXT(35); + ASSERT_NEXT(SI_FINISHED); + ASSERT_SIT; + + it = setup_simple_iterator(42, "\003\004", 2); + ff_it = it = new_fast_forward_iterator(it, 41); + ASSERT_FFIT; + ASSERT_CUE(1); + ASSERT_FFIT; + ASSERT_CUE(2); + ASSERT_FFIT; + ASSERT_NEXT(1); + ASSERT_NEXT(SI_FINISHED); + ASSERT_SIT; + + puts("[TEST] Test OK."); +} + +#define SIMPLE_SONG_SIZE 50 + +static unsigned char simple_song[SIMPLE_SONG_SIZE] = { + 0x00, /* Regular song */ + /* Only use channel 0 for all devices */ + 0x02, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Song begins here */ + 42, 0x90, 60, 0x7f, /* Play C after 42 ticks */ + 02, 64, 0x42, /* Play E after 2 more ticks, using running status mode */ + 0xf8, 10, 0x80, 60, 0x02, /* Stop C after 250 ticks */ + 0, 64, 0x00, /* Stop E immediately */ + 00, 0xfc /* Stop song */ +}; + +#define ASSERT_MIDI3(cmd, arg0, arg1) \ + ASSERT(data[0] == cmd); \ + ASSERT(data[1] == arg0); \ + ASSERT(data[2] == arg1); + +void test_iterator_sci0() { + SongIterator *it = songit_new(simple_song, SIMPLE_SONG_SIZE, SCI_SONG_ITERATOR_TYPE_SCI0, 0l); + unsigned char data[4]; + int result; + SIMSG_SEND(it, SIMSG_SET_PLAYMASK(0x0001)); /* Initialise song, enabling channel 0 */ + + puts("[TEST] SCI0-style song"); + ASSERT_NEXT(42); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x90, 60, 0x7f); + ASSERT_NEXT(2); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x90, 64, 0x42); + ASSERT_NEXT(250); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x80, 60, 0x02); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x80, 64, 0x00); + ASSERT_NEXT(SI_FINISHED); + puts("[TEST] Test OK."); +} + + + +void test_iterator_sci0_loop() { + SongIterator *it = songit_new(simple_song, SIMPLE_SONG_SIZE, SCI_SONG_ITERATOR_TYPE_SCI0, 0l); + unsigned char data[4]; + int result; + SIMSG_SEND(it, SIMSG_SET_PLAYMASK(0x0001)); /* Initialise song, enabling channel 0 */ + SIMSG_SEND(it, SIMSG_SET_LOOPS(2)); /* Loop one additional time */ + + puts("[TEST] SCI0-style song with looping"); + ASSERT_NEXT(42); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x90, 60, 0x7f); + ASSERT_NEXT(2); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x90, 64, 0x42); + ASSERT_NEXT(250); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x80, 60, 0x02); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x80, 64, 0x00); + ASSERT_NEXT(SI_LOOP); + ASSERT_NEXT(42); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x90, 60, 0x7f); + ASSERT_NEXT(2); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x90, 64, 0x42); + ASSERT_NEXT(250); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x80, 60, 0x02); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x80, 64, 0x00); + ASSERT_NEXT(SI_FINISHED); + puts("[TEST] Test OK."); +} + + + +#define LOOP_SONG_SIZE 54 + +unsigned char loop_song[LOOP_SONG_SIZE] = { + 0x00, /* Regular song song */ + /* Only use channel 0 for all devices */ + 0x02, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Song begins here */ + 42, 0x90, 60, 0x7f, /* Play C after 42 ticks */ + 13, 0x80, 60, 0x00, /* Stop C after 13 ticks */ + 00, 0xCF, 0x7f, /* Set loop point */ + 02, 0x90, 64, 0x42, /* Play E after 2 more ticks, using running status mode */ + 03, 0x80, 64, 0x00, /* Stop E after 3 ticks */ + 00, 0xfc /* Stop song/loop */ +}; + + +void test_iterator_sci0_mark_loop() { + SongIterator *it = songit_new(loop_song, LOOP_SONG_SIZE, SCI_SONG_ITERATOR_TYPE_SCI0, 0l); + unsigned char data[4]; + int result; + SIMSG_SEND(it, SIMSG_SET_PLAYMASK(0x0001)); /* Initialise song, enabling channel 0 */ + SIMSG_SEND(it, SIMSG_SET_LOOPS(3)); /* Loop once more */ + + puts("[TEST] SCI0-style song with loop mark, looping"); + ASSERT_NEXT(42); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x90, 60, 0x7f); + ASSERT_NEXT(13); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x80, 60, 0x00); + /* Loop point here: we don't observe that in the iterator interface yet, though */ + ASSERT_NEXT(2); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x90, 64, 0x42); + ASSERT_NEXT(3); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x80, 64, 0x00); + /* Now we loop back to the loop pont */ + ASSERT_NEXT(SI_LOOP); + ASSERT_NEXT(2); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x90, 64, 0x42); + ASSERT_NEXT(3); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x80, 64, 0x00); + /* ...and one final time */ + ASSERT_NEXT(SI_LOOP); + ASSERT_NEXT(2); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x90, 64, 0x42); + ASSERT_NEXT(3); + ASSERT_NEXT(0); + ASSERT_MIDI3(0x80, 64, 0x00); + + ASSERT_NEXT(SI_FINISHED); + puts("[TEST] Test OK."); +} + + + +int main(int argc, char **argv) { + test_simple_it(); + test_fastforward(); + test_iterator_sci0(); + test_iterator_sci0_loop(); + test_iterator_sci0_mark_loop(); + if (errors != 0) + warning("[ERROR] %d errors total", errors); + return (errors != 0); +} diff --git a/engines/scumm/charset.cpp b/engines/scumm/charset.cpp index fa4804ce7d..053bf597f8 100644 --- a/engines/scumm/charset.cpp +++ b/engines/scumm/charset.cpp @@ -844,7 +844,7 @@ void CharsetRendererClassic::printChar(int chr, bool ignoreCharsetMask) { offsX = offsY = 0; } else { uint32 charOffs = READ_LE_UINT32(_fontPtr + chr * 4 + 4); - assert(charOffs < 0x14000); + assert(charOffs < 0x10000); if (!charOffs) return; charPtr = _fontPtr + charOffs; diff --git a/engines/scumm/debugger.cpp b/engines/scumm/debugger.cpp index b5a4070f0b..e0582f79ef 100644 --- a/engines/scumm/debugger.cpp +++ b/engines/scumm/debugger.cpp @@ -868,7 +868,7 @@ bool ScummDebugger::Cmd_Passcode(int argc, const char **argv) { detach(); } else { - DebugPrintf("Current Passcode is %d \nUse 'passcode <SEGA CD Passcode>'\n",_vm->_scummVars[411]); + DebugPrintf("Use 'passcode <SEGA CD Passcode>'\n"); return true; } return false; diff --git a/engines/scumm/dialogs.cpp b/engines/scumm/dialogs.cpp index 1e0bf6d4be..b160dac6f2 100644 --- a/engines/scumm/dialogs.cpp +++ b/engines/scumm/dialogs.cpp @@ -234,6 +234,19 @@ protected: #endif +class ConfigDialog : public GUI::OptionsDialog { +protected: +#ifdef SMALL_SCREEN_DEVICE + GUI::Dialog *_keysDialog; +#endif + +public: + ConfigDialog(); + ~ConfigDialog(); + + virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data); +}; + #pragma mark - ScummDialog::ScummDialog(int x, int y, int w, int h) : GUI::Dialog(x, y, w, h) { @@ -246,31 +259,223 @@ ScummDialog::ScummDialog(String name) : GUI::Dialog(name) { #pragma mark - -#ifndef DISABLE_HELP +enum { + kSaveCmd = 'SAVE', + kLoadCmd = 'LOAD', + kPlayCmd = 'PLAY', + kOptionsCmd = 'OPTN', + kHelpCmd = 'HELP', + kAboutCmd = 'ABOU', + kQuitCmd = 'QUIT', + kChooseCmd = 'CHOS' +}; ScummMenuDialog::ScummMenuDialog(ScummEngine *scumm) - : MainMenuDialog(scumm) { + : ScummDialog("ScummMain"), _vm(scumm) { + + new GUI::ButtonWidget(this, "ScummMain.Resume", "Resume", kPlayCmd, 'P'); + + _loadButton = new GUI::ButtonWidget(this, "ScummMain.Load", "Load", kLoadCmd, 'L'); + _saveButton = new GUI::ButtonWidget(this, "ScummMain.Save", "Save", kSaveCmd, 'S'); + + new GUI::ButtonWidget(this, "ScummMain.Options", "Options", kOptionsCmd, 'O'); +#ifndef DISABLE_HELP + new GUI::ButtonWidget(this, "ScummMain.Help", "Help", kHelpCmd, 'H'); +#endif + new GUI::ButtonWidget(this, "ScummMain.About", "About", kAboutCmd, 'A'); + + new GUI::ButtonWidget(this, "ScummMain.Quit", "Quit", kQuitCmd, 'Q'); + + // + // Create the sub dialog(s) + // + _aboutDialog = new GUI::AboutDialog(); + _optionsDialog = new ConfigDialog(); +#ifndef DISABLE_HELP _helpDialog = new HelpDialog(scumm->_game); - _helpButton->setEnabled(true); +#endif + _saveDialog = new GUI::SaveLoadChooser("Save game:", "Save"); + _saveDialog->setSaveMode(true); + _loadDialog = new GUI::SaveLoadChooser("Load game:", "Load"); + _loadDialog->setSaveMode(false); } ScummMenuDialog::~ScummMenuDialog() { + delete _aboutDialog; + delete _optionsDialog; +#ifndef DISABLE_HELP delete _helpDialog; +#endif + delete _saveDialog; + delete _loadDialog; +} + +int ScummMenuDialog::runModal() { + _loadButton->setEnabled(_vm->canLoadGameStateCurrently()); + _saveButton->setEnabled(_vm->canSaveGameStateCurrently()); + return ScummDialog::runModal(); +} + +void ScummMenuDialog::reflowLayout() { + _loadButton->setEnabled(_vm->canLoadGameStateCurrently()); + _saveButton->setEnabled(_vm->canSaveGameStateCurrently()); + Dialog::reflowLayout(); } void ScummMenuDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { switch (cmd) { + case kSaveCmd: + save(); + break; + case kLoadCmd: + load(); + break; + case kPlayCmd: + close(); + break; + case kOptionsCmd: + _optionsDialog->runModal(); + break; + case kAboutCmd: + _aboutDialog->runModal(); + break; +#ifndef DISABLE_HELP case kHelpCmd: _helpDialog->runModal(); break; +#endif + case kQuitCmd: + _vm->quitGame(); + close(); + break; default: - MainMenuDialog::handleCommand(sender, cmd, data); + ScummDialog::handleCommand(sender, cmd, data); + } +} + +void ScummMenuDialog::save() { + Common::String gameId = ConfMan.get("gameid"); + + const EnginePlugin *plugin = 0; + EngineMan.findGame(gameId, &plugin); + + int idx = _saveDialog->runModal(plugin, ConfMan.getActiveDomainName()); + if (idx >= 0) { + String result(_saveDialog->getResultString()); + char buffer[20]; + const char *str; + if (result.empty()) { + // If the user was lazy and entered no save name, come up with a default name. + sprintf(buffer, "Save %d", idx); + str = buffer; + } else + str = result.c_str(); + _vm->requestSave(idx, str); + close(); + } +} + +void ScummMenuDialog::load() { + Common::String gameId = ConfMan.get("gameid"); + + const EnginePlugin *plugin = 0; + EngineMan.findGame(gameId, &plugin); + + int idx = _loadDialog->runModal(plugin, ConfMan.getActiveDomainName()); + if (idx >= 0) { + _vm->requestLoad(idx); + close(); } } #pragma mark - enum { + kKeysCmd = 'KEYS' +}; + +// FIXME: We use the empty string as domain name here. This tells the +// ConfigManager to use the 'default' domain for all its actions. We do that +// to get as close as possible to editing the 'active' settings. +// +// However, that requires bad & evil hacks in the ConfigManager code, +// and even then still doesn't work quite correctly. +// For example, if the transient domain contains 'false' for the 'fullscreen' +// flag, but the user used a hotkey to switch to windowed mode, then the dialog +// will display the wrong value anyway. +// +// Proposed solution consisting of multiple steps: +// 1) Add special code to the open() code that reads out everything stored +// in the transient domain that is controlled by this dialog, and updates +// the dialog accordingly. +// 2) Even more code is added to query the backend for current settings, like +// the fullscreen mode flag etc., and also updates the dialog accordingly. +// 3) The domain being edited is set to the active game domain. +// 4) If the dialog is closed with the "OK" button, then we remove everything +// stored in the transient domain (or at least everything corresponding to +// switches in this dialog. +// If OTOH the dialog is closed with "Cancel" we do no such thing. +// +// These changes will achieve two things at once: Allow us to get rid of using +// "" as value for the domain, and in fact provide a somewhat better user +// experience at the same time. +ConfigDialog::ConfigDialog() + : GUI::OptionsDialog("", "ScummConfig") { + + // + // Sound controllers + // + + addVolumeControls(this, "ScummConfig."); + + // + // Some misc options + // + + // SCUMM has a talkspeed range of 0-9 + addSubtitleControls(this, "ScummConfig.", 9); + + // + // Add the buttons + // + + new GUI::ButtonWidget(this, "ScummConfig.Ok", "OK", GUI::kOKCmd, 'O'); + new GUI::ButtonWidget(this, "ScummConfig.Cancel", "Cancel", GUI::kCloseCmd, 'C'); +#ifdef SMALL_SCREEN_DEVICE + new GUI::ButtonWidget(this, "ScummConfig.Keys", "Keys", kKeysCmd, 'K'); + _keysDialog = NULL; +#endif +} + +ConfigDialog::~ConfigDialog() { +#ifdef SMALL_SCREEN_DEVICE + delete _keysDialog; +#endif +} + +void ConfigDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { + switch (cmd) { + case kKeysCmd: +#ifdef SMALL_SCREEN_DEVICE + // + // Create the sub dialog(s) + // + _keysDialog = new GUI::KeysDialog(); + _keysDialog->runModal(); + delete _keysDialog; + _keysDialog = NULL; +#endif + break; + default: + GUI::OptionsDialog::handleCommand (sender, cmd, data); + } +} + +#ifndef DISABLE_HELP + +#pragma mark - + +enum { kNextCmd = 'NEXT', kPrevCmd = 'PREV' }; diff --git a/engines/scumm/dialogs.h b/engines/scumm/dialogs.h index 41a8ec83c1..7889027dcf 100644 --- a/engines/scumm/dialogs.h +++ b/engines/scumm/dialogs.h @@ -27,8 +27,9 @@ #include "common/str.h" #include "gui/dialog.h" +#include "gui/options.h" #include "gui/widget.h" -#include "engines/dialogs.h" +#include "gui/saveload.h" #include "scumm/detection.h" @@ -51,17 +52,32 @@ protected: typedef Common::String String; }; -#ifndef DISABLE_HELP -class ScummMenuDialog : public MainMenuDialog { +class ScummMenuDialog : public ScummDialog { public: ScummMenuDialog(ScummEngine *scumm); ~ScummMenuDialog(); virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data); + virtual void reflowLayout(); + + int runModal(); protected: + ScummEngine *_vm; + + GUI::Dialog *_aboutDialog; + GUI::Dialog *_optionsDialog; +#ifndef DISABLE_HELP GUI::Dialog *_helpDialog; -}; #endif + GUI::SaveLoadChooser *_saveDialog; + GUI::SaveLoadChooser *_loadDialog; + + GUI::ButtonWidget *_loadButton; + GUI::ButtonWidget *_saveButton; + + void save(); + void load(); +}; /** * A dialog which displays an arbitrary message to the user and returns diff --git a/engines/scumm/he/resource_he.cpp b/engines/scumm/he/resource_he.cpp index c259c3ffd2..886ee99e57 100644 --- a/engines/scumm/he/resource_he.cpp +++ b/engines/scumm/he/resource_he.cpp @@ -633,10 +633,8 @@ Win32ResExtractor::WinResource *Win32ResExtractor::list_pe_resources(WinLibrary wr[c].children = fi->first_resource + (FROM_LE_32(dirent[c].offset_to_data) & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY); /* fill in wr->id, wr->numeric_id */ - if (!decode_pe_resource_id(fi, wr + c, FROM_LE_32(dirent[c].name))) { - free(wr); + if (!decode_pe_resource_id(fi, wr + c, FROM_LE_32(dirent[c].name))) return NULL; - } } return wr; diff --git a/engines/scumm/input.cpp b/engines/scumm/input.cpp index dc3a5d26b3..8a9570f534 100644 --- a/engines/scumm/input.cpp +++ b/engines/scumm/input.cpp @@ -508,7 +508,7 @@ void ScummEngine::processKeyboard(Common::KeyState lastKeyHit) { if (VAR_SAVELOAD_SCRIPT != 0xFF && _currentRoom != 0) runScript(VAR(VAR_SAVELOAD_SCRIPT), 0, 0, 0); - openMainMenuDialog(); // Display global main menu + scummMenuDialog(); // Display GUI if (VAR_SAVELOAD_SCRIPT != 0xFF && _currentRoom != 0) runScript(VAR(VAR_SAVELOAD_SCRIPT2), 0, 0, 0); diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index fc95060b6f..dcbe4e6c0a 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -108,7 +108,7 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr) _language(dr.language), _debugger(0), _currentScript(0xFF), // Let debug() work on init stage - _messageDialog(0), _pauseDialog(0), _versionDialog(0) { + _messageDialog(0), _pauseDialog(0), _scummMenuDialog(0), _versionDialog(0) { if (_game.platform == Common::kPlatformNES) { _gdi = new GdiNES(this); @@ -140,6 +140,7 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr) _fileHandle = 0; + // Init all vars _v0ObjectIndex = false; _v0ObjectInInventory = false; @@ -151,6 +152,7 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr) _sound = NULL; memset(&vm, 0, sizeof(vm)); _pauseDialog = NULL; + _scummMenuDialog = NULL; _versionDialog = NULL; _fastMode = 0; _actors = NULL; @@ -550,12 +552,6 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr) for (int i = 0; i < ARRAYSIZE(debugChannels); ++i) DebugMan.addDebugChannel(debugChannels[i].flag, debugChannels[i].channel, debugChannels[i].desc); -#ifndef DISABLE_HELP - // Create custom GMM dialog providing a help subdialog - assert(!_mainMenuDialog); - _mainMenuDialog = new ScummMenuDialog(this); -#endif - g_eventRec.registerRandomSource(_rnd, "scumm"); } @@ -576,6 +572,7 @@ ScummEngine::~ScummEngine() { delete _charset; delete _messageDialog; delete _pauseDialog; + delete _scummMenuDialog; delete _versionDialog; delete _fileHandle; @@ -2447,6 +2444,13 @@ void ScummEngine::versionDialog() { runDialog(*_versionDialog); } +void ScummEngine::scummMenuDialog() { + if (!_scummMenuDialog) + _scummMenuDialog = new ScummMenuDialog(this); + runDialog(*_scummMenuDialog); + syncSoundSettings(); +} + void ScummEngine::confirmExitDialog() { ConfirmDialog d(this, 6); diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h index 42322ba5a2..885ab790de 100644 --- a/engines/scumm/scumm.h +++ b/engines/scumm/scumm.h @@ -530,6 +530,7 @@ protected: Dialog *_pauseDialog; Dialog *_messageDialog; Dialog *_versionDialog; + Dialog *_scummMenuDialog; virtual int runDialog(Dialog &dialog); void confirmExitDialog(); @@ -537,6 +538,7 @@ protected: void pauseDialog(); void messageDialog(const char *message); void versionDialog(); + void scummMenuDialog(); char displayMessage(const char *altButton, const char *message, ...) GCC_PRINTF(3, 4); diff --git a/engines/sword1/animation.cpp b/engines/sword1/animation.cpp index 441e622184..c0e7be7758 100644 --- a/engines/sword1/animation.cpp +++ b/engines/sword1/animation.cpp @@ -23,6 +23,7 @@ * */ + #include "common/file.h" #include "sword1/sword1.h" #include "sword1/animation.h" @@ -71,9 +72,6 @@ MoviePlayer::MoviePlayer(SwordEngine *vm, Text *textMan, Audio::Mixer *snd, OSys _bgSoundStream = NULL; _decoderType = decoderType; _decoder = decoder; - - _white = 255; - _black = 0; } MoviePlayer::~MoviePlayer() { @@ -256,35 +254,9 @@ bool MoviePlayer::playVideo() { if (frame) _vm->_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, x, y, frame->w, frame->h); - if (_decoder->hasDirtyPalette()) { + if (_decoder->hasDirtyPalette()) _decoder->setSystemPalette(); - uint32 maxWeight = 0; - uint32 minWeight = 0xFFFFFFFF; - uint32 weight; - byte r, g, b; - - byte *palette = _decoder->getPalette(); - - for (int i = 0; i < 256; i++) { - r = *palette++; - g = *palette++; - b = *palette++; - - weight = 3 * r * r + 6 * g * g + 2 * b * b; - - if (weight >= maxWeight) { - maxWeight = weight; - _white = i; - } - - if (weight <= minWeight) { - minWeight = weight; - _black = i; - } - } - } - Graphics::Surface *screen = _vm->_system->lockScreen(); performPostProcessing((byte *)screen->pixels); _vm->_system->unlockScreen(); @@ -295,19 +267,17 @@ bool MoviePlayer::playVideo() { while (_vm->_system->getEventManager()->pollEvent(event)) if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP) return false; - - _vm->_system->delayMillis(10); } return !_vm->shouldQuit(); } byte MoviePlayer::findBlackPalIndex() { - return _black; + return 0; } byte MoviePlayer::findWhitePalIndex() { - return _white; + return 0xff; } DXADecoderWithSound::DXADecoderWithSound(Audio::Mixer *mixer, Audio::SoundHandle *bgSoundHandle) diff --git a/engines/sword1/animation.h b/engines/sword1/animation.h index 193d5cf7ca..82343f2800 100644 --- a/engines/sword1/animation.h +++ b/engines/sword1/animation.h @@ -85,7 +85,6 @@ protected: OSystem *_system; Common::Array<MovieText *> _movieTexts; int _textX, _textY, _textWidth, _textHeight; - byte _white, _black; DecoderType _decoderType; Graphics::VideoDecoder *_decoder; diff --git a/engines/sword2/animation.cpp b/engines/sword2/animation.cpp index 10895b2ec1..c3f3e796b2 100644 --- a/engines/sword2/animation.cpp +++ b/engines/sword2/animation.cpp @@ -51,9 +51,6 @@ MoviePlayer::MoviePlayer(Sword2Engine *vm, Audio::Mixer *snd, OSystem *system, A _bgSoundStream = NULL; _decoderType = decoderType; _decoder = decoder; - - _white = 255; - _black = 0; } MoviePlayer:: ~MoviePlayer() { @@ -283,35 +280,9 @@ bool MoviePlayer::playVideo() { if (frame) _vm->_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, x, y, frame->w, frame->h); - if (_decoder->hasDirtyPalette()) { + if (_decoder->hasDirtyPalette()) _decoder->setSystemPalette(); - uint32 maxWeight = 0; - uint32 minWeight = 0xFFFFFFFF; - uint32 weight; - byte r, g, b; - - byte *palette = _decoder->getPalette(); - - for (int i = 0; i < 256; i++) { - r = *palette++; - g = *palette++; - b = *palette++; - - weight = 3 * r * r + 6 * g * g + 2 * b * b; - - if (weight >= maxWeight) { - maxWeight = weight; - _white = i; - } - - if (weight <= minWeight) { - minWeight = weight; - _black = i; - } - } - } - Graphics::Surface *screen = _vm->_system->lockScreen(); performPostProcessing((byte *)screen->pixels); _vm->_system->unlockScreen(); @@ -322,19 +293,17 @@ bool MoviePlayer::playVideo() { while (_vm->_system->getEventManager()->pollEvent(event)) if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP) return false; - - _vm->_system->delayMillis(10); } return !_vm->shouldQuit(); } byte MoviePlayer::findBlackPalIndex() { - return _black; + return 0; } byte MoviePlayer::findWhitePalIndex() { - return _white; + return 0xff; } DXADecoderWithSound::DXADecoderWithSound(Audio::Mixer *mixer, Audio::SoundHandle *bgSoundHandle) diff --git a/engines/sword2/animation.h b/engines/sword2/animation.h index ee32b1d5f2..bbf83e264c 100644 --- a/engines/sword2/animation.h +++ b/engines/sword2/animation.h @@ -87,7 +87,6 @@ protected: uint32 _currentMovieText; byte *_textSurface; int _textX, _textY; - byte _white, _black; DecoderType _decoderType; Graphics::VideoDecoder *_decoder; diff --git a/engines/tinsel/handle.cpp b/engines/tinsel/handle.cpp index fdc4484a7c..df686f8bee 100644 --- a/engines/tinsel/handle.cpp +++ b/engines/tinsel/handle.cpp @@ -284,8 +284,7 @@ void LoadFile(MEMHANDLE *pH) { } // extract and zero terminate the filename - memcpy(szFilename, pH->szName, sizeof(pH->szName)); - szFilename[sizeof(pH->szName)] = 0; + Common::strlcpy(szFilename, pH->szName, sizeof(pH->szName)); if (f.open(szFilename)) { // read the data diff --git a/gui/launcher.cpp b/gui/launcher.cpp index d50e7ce578..bc5debd9cd 100644 --- a/gui/launcher.cpp +++ b/gui/launcher.cpp @@ -733,7 +733,7 @@ void LauncherDialog::addGame() { // ...so let's determine a list of candidates, games that // could be contained in the specified directory. GameList candidates(EngineMan.detectGames(files)); - + int idx; if (candidates.empty()) { // No game was found in the specified directory @@ -874,7 +874,12 @@ void LauncherDialog::loadGame(int item) { gameId = _domains[item]; const EnginePlugin *plugin = 0; + +#if defined(NEW_PLUGIN_DESIGN_FIRST_REFINEMENT) && defined(DYNAMIC_MODULES) + EngineMan.findGameOnePlugAtATime(gameId, &plugin); +#else EngineMan.findGame(gameId, &plugin); +#endif String target = _domains[item]; target.toLowercase(); diff --git a/gui/options.cpp b/gui/options.cpp index 072b20b393..d1901e9219 100644 --- a/gui/options.cpp +++ b/gui/options.cpp @@ -770,7 +770,7 @@ void OptionsDialog::addMT32Controls(GuiObject *boss, const Common::String &prefi _mt32DevicePopUp->setEnabled(false); } - _enableMIDISettings = true; + _enableMT32Settings = true; } // The function has an extra slider range parameter, since both the launcher and SCUMM engine diff --git a/test/common/str.h b/test/common/str.h index 6581c37cdb..16fb0859db 100644 --- a/test/common/str.h +++ b/test/common/str.h @@ -118,30 +118,6 @@ class StringTestSuite : public CxxTest::TestSuite TS_ASSERT_EQUALS(foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); } - void test_refCount5() { - // using external storage - Common::String foo1("HelloHelloHelloHelloAndHi"); - Common::String foo2(foo1); - - for (Common::String::iterator i = foo2.begin(); i != foo2.end(); ++i) - *i = 'h'; - - TS_ASSERT_EQUALS(foo1, "HelloHelloHelloHelloAndHi"); - TS_ASSERT_EQUALS(foo2, "hhhhhhhhhhhhhhhhhhhhhhhhh"); - } - - void test_refCount6() { - // using internal storage - Common::String foo1("Hello"); - Common::String foo2(foo1); - - for (Common::String::iterator i = foo2.begin(); i != foo2.end(); ++i) - *i = 'h'; - - TS_ASSERT_EQUALS(foo1, "Hello"); - TS_ASSERT_EQUALS(foo2, "hhhhh"); - } - void test_self_asignment() { Common::String foo1("12345678901234567890123456789012"); foo1 = foo1.c_str() + 2; diff --git a/tools/create_msvc/create_msvc.cpp b/tools/create_msvc/create_msvc.cpp index 1c395b01aa..fcae638c50 100644 --- a/tools/create_msvc/create_msvc.cpp +++ b/tools/create_msvc/create_msvc.cpp @@ -512,6 +512,8 @@ int main(int argc, char *argv[]) { // 4103 (alignment changed after including header, may be due to missing #pragma pack(pop)) // used by pack-start / pack-end // + // 4121 (alignment of a member was sensitive to packing) + // // 4127 (conditional expression is constant) // used in a lot of engines // @@ -565,6 +567,7 @@ int main(int argc, char *argv[]) { projectWarnings["lure"] = "4189;4355"; projectWarnings["kyra"] = "4355"; projectWarnings["m4"] = "4355"; + projectWarnings["mohawk"] = "4121"; ProjectProvider *provider = NULL; diff --git a/tools/md5table.c b/tools/md5table.c index 7d76b7541d..cb2959ed88 100644 --- a/tools/md5table.c +++ b/tools/md5table.c @@ -222,7 +222,7 @@ int main(int argc, char *argv[]) const int entrySize = 256; int numEntries = 0, maxEntries = 1; - char *entriesBuffer = (char *)malloc(maxEntries * entrySize); + char *entriesBuffer = malloc(maxEntries * entrySize); typedef enum { kCPPOutput, @@ -295,7 +295,7 @@ int main(int argc, char *argv[]) } else if (entry.md5) { if (numEntries >= maxEntries) { maxEntries *= 2; - entriesBuffer = (char *)realloc(entriesBuffer, maxEntries * entrySize); + entriesBuffer = realloc(entriesBuffer, maxEntries * entrySize); } if (0 == strcmp(entry.variant, "-")) entry.variant = ""; diff --git a/tools/module.mk b/tools/module.mk index 2c62e427ea..5248454382 100644 --- a/tools/module.mk +++ b/tools/module.mk @@ -36,15 +36,15 @@ clean-tools: tools/convbdf$(EXEEXT): $(srcdir)/tools/convbdf.c $(QUIET)$(MKDIR) tools/$(DEPDIR) - $(QUIET_LINK)$(LD) $(CFLAGS) -Wall -o $@ $< + $(QUIET_LINK)$(CC) $(CFLAGS) -Wall -o $@ $< tools/md5table$(EXEEXT): $(srcdir)/tools/md5table.c $(QUIET)$(MKDIR) tools/$(DEPDIR) - $(QUIET_LINK)$(LD) $(CFLAGS) -Wall -o $@ $< + $(QUIET_LINK)$(CC) $(CFLAGS) -Wall -o $@ $< tools/make-scumm-fontdata$(EXEEXT): $(srcdir)/tools/make-scumm-fontdata.c $(QUIET)$(MKDIR) tools/$(DEPDIR) - $(QUIET_LINK)$(LD) $(CFLAGS) -Wall -o $@ $< + $(QUIET_LINK)$(CC) $(CFLAGS) -Wall -o $@ $< # # Rules to explicitly rebuild the credits / MD5 tables. diff --git a/tools/scumm-md5.txt b/tools/scumm-md5.txt index c3e48d6c5a..b379bc837e 100644 --- a/tools/scumm-md5.txt +++ b/tools/scumm-md5.txt @@ -591,7 +591,6 @@ catalog Humongous Interactive Catalog airport Let's Explore the Airport with Buzzy d6334a5a9b61afe18c368540fdf522ca -1 en Mac - - - Joachim Eberhard 07433205acdca3bc553d0e731588b35f -1 en Windows - - - Kirben - 3e861421f494711bc6f619d4aba60285 93231 ru Windows - - - sev 7ea2da67ebabea4ac20cee9f4f9d2934 -1 en Mac - Demo - khalek 8ffd618a776a4c0d8922bb28b09f8ce8 -1 en Windows - Demo - khalek @@ -603,7 +602,6 @@ farm Let's Explore the Farm with Buzzy a5c5388da9bf0e6662fdca8813a79d13 86962 en Windows - - - George Kormendi a85856675429fe88051744f755b72f93 -1 en Windows - - - Kirben a2386da005672cbd5136f4f27a626c5f 87061 nl Windows - - - George Kormendi - 5dda73606533d66a4c3f4f9ea6e842af 87061 ru Windows - - - sev 39fd6db10d0222d817025c4d3346e3b4 -1 en Mac - Demo - Joachim Eberhard bf8b52fdd9a69c67f34e8e9fec72661c -1 en Windows HE 71 Demo - khalek, sev @@ -715,7 +713,6 @@ puttmoon Putt-Putt Goes to the Moon 697c9b7c55a05d8199c48b48e379d2c8 -1 he DOS - - - sev 9dc02577bf50d4cfaf3de3fbac06fbe2 -1 en Mac - - - khalek 9c92eeaf517a31b7221ec2546ab669fd -1 en Windows HE 70 - - khalek - 3c4c471342bd95505a42334367d8f127 12161 ru Windows HE 70 - - sev aa6a91b7f6f119d1b7b1f2a4c9e24d59 6233 en DOS - Demo - 4af4a6b248103c1fe9edef619677f540 -1 en Mac - Demo - khalek |