/* ScummVM - Scumm Interpreter * Copyright (C) 2004 The ScummVM project * * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Header$ * */ // Background animation management module #include "saga/saga.h" #include "saga/gfx.h" #include "saga/cvar_mod.h" #include "saga/console.h" #include "saga/game_mod.h" #include "saga/events.h" #include "saga/render.h" #include "saga/animation.h" namespace Saga { static void CF_anim_info(int argc, char *argv[], void *refCon); int Anim::reg() { CVAR_RegisterFunc(CF_anim_info, "anim_info", NULL, CVAR_NONE, 0, 0, this); return SUCCESS; } Anim::Anim(SagaEngine *vm) : _vm(vm) { int i; _anim_limit = MAX_ANIMATIONS; _anim_count = 0; for (i = 0; i < MAX_ANIMATIONS; i++) _anim_tbl[i] = NULL; _initialized = true; } Anim::~Anim(void) { uint16 i; for (i = 0; i < MAX_ANIMATIONS; i++) free(_anim_tbl[i]); _initialized = false; } int Anim::load(const byte *anim_resdata, size_t anim_resdata_len, uint16 *anim_id_p) { ANIMATION *new_anim; uint16 anim_id = 0; uint16 i; if (!_initialized) { warning("Anim::load not initialised"); return FAILURE; } // Find an unused animation slot for (i = 0; i < MAX_ANIMATIONS; i++) { if (_anim_tbl[i] == NULL) { anim_id = i; break; } } if (i == MAX_ANIMATIONS) { warning("Anim::load could not find unused animation slot"); return FAILURE; } new_anim = (ANIMATION *)malloc(sizeof *new_anim); if (new_anim == NULL) { warning("Anim::load Allocation failure"); return MEM; } new_anim->resdata = anim_resdata; new_anim->resdata_len = anim_resdata_len; if (GAME_GetGameType() == GID_ITE) { if (getNumFrames(anim_resdata, anim_resdata_len, &new_anim->n_frames) != SUCCESS) { warning("Anim::load Couldn't get animation frame count"); return FAILURE; } // Cache frame offsets new_anim->frame_offsets = (size_t *)malloc(new_anim->n_frames * sizeof *new_anim->frame_offsets); if (new_anim->frame_offsets == NULL) { warning("Anim::load Allocation failure"); return MEM; } for (i = 0; i < new_anim->n_frames; i++) { getFrameOffset(anim_resdata, anim_resdata_len, i + 1, &new_anim->frame_offsets[i]); } } else { new_anim->cur_frame_p = anim_resdata + SAGA_FRAME_HEADER_LEN; new_anim->cur_frame_len = anim_resdata_len - SAGA_FRAME_HEADER_LEN; getNumFrames(anim_resdata, anim_resdata_len, &new_anim->n_frames); } // Set animation data new_anim->current_frame = 1; new_anim->end_frame = new_anim->n_frames; new_anim->stop_frame = new_anim->end_frame; new_anim->frame_time = DEFAULT_FRAME_TIME; new_anim->flags = 0; new_anim->play_flag = 0; new_anim->link_flag = 0; new_anim->link_id = 0; _anim_tbl[anim_id] = new_anim; *anim_id_p = anim_id; _anim_count++; return SUCCESS; } int Anim::link(uint16 anim_id1, uint16 anim_id2) { ANIMATION *anim1; ANIMATION *anim2; if ((anim_id1 >= _anim_count) || (anim_id2 >= _anim_count)) { return FAILURE; } anim1 = _anim_tbl[anim_id1]; anim2 = _anim_tbl[anim_id2]; if ((anim1 == NULL) || (anim2 == NULL)) { return FAILURE; } anim1->link_id = anim_id2; anim1->link_flag = 1; anim2->frame_time = anim1->frame_time; return SUCCESS; } int Anim::play(uint16 anim_id, int vector_time) { EVENT event; ANIMATION *anim; ANIMATION *link_anim; uint16 link_anim_id; BUFFER_INFO buf_info; byte *display_buf; const byte *nextf_p; size_t nextf_len; uint16 frame; int result; GAME_DISPLAYINFO disp_info; if (anim_id >= _anim_count) { return FAILURE; } GAME_GetDisplayInfo(&disp_info); _vm->_render->getBufferInfo(&buf_info); display_buf = buf_info.bg_buf; anim = _anim_tbl[anim_id]; if (anim == NULL) { return FAILURE; } if (anim->flags & ANIM_PAUSE) return SUCCESS; if (anim->play_flag) { frame = anim->current_frame; if (GAME_GetGameType() == GID_ITE) { result = ITE_DecodeFrame(anim->resdata, anim->resdata_len, anim->frame_offsets[frame - 1], display_buf, disp_info.logical_w * disp_info.logical_h); if (result != SUCCESS) { warning("ANIM::play: Error decoding frame %u", anim->current_frame); anim->play_flag = 0; return FAILURE; } } else { if (anim->cur_frame_p == NULL) { warning("ANIM::play: Frames exhausted"); return FAILURE; } result = IHNM_DecodeFrame(display_buf, disp_info.logical_w * disp_info.logical_h, anim->cur_frame_p, anim->cur_frame_len, &nextf_p, &nextf_len); if (result != SUCCESS) { warning("ANIM::play: Error decoding frame %u", anim->current_frame); anim->play_flag = 0; return FAILURE; } anim->cur_frame_p = nextf_p; anim->cur_frame_len = nextf_len; } anim->current_frame++; } anim->play_flag = 1; if (anim->current_frame > anim->n_frames) { // Animation done playing if (anim->link_flag) { // If this animation has a link, follow it anim->play_flag = 0; anim->current_frame = 1; link_anim_id = anim->link_id; link_anim = _anim_tbl[link_anim_id]; if (link_anim != NULL) { link_anim->current_frame = 1; link_anim->play_flag = 1; } anim_id = link_anim_id; } else if (anim->flags & ANIM_LOOP) { // Loop animation anim->current_frame = 1; anim->cur_frame_p = anim->resdata + SAGA_FRAME_HEADER_LEN; anim->cur_frame_len = anim->resdata_len - SAGA_FRAME_HEADER_LEN; } else { // No link, stop playing anim->current_frame = anim->n_frames; anim->play_flag = 0; if (anim->flags & ANIM_ENDSCENE) { // This animation ends the scene event.type = ONESHOT_EVENT; event.code = SCENE_EVENT; event.op = EVENT_END; event.time = anim->frame_time + vector_time; _vm->_events->queue(&event); } return SUCCESS; } } event.type = ONESHOT_EVENT; event.code = ANIM_EVENT; event.op = EVENT_FRAME; event.param = anim_id; event.time = anim->frame_time + vector_time; _vm->_events->queue(&event); return SUCCESS; } int Anim::reset() { uint16 i; for (i = 0; i < MAX_ANIMATIONS; i++) { freeId(i); } _anim_count = 0; return SUCCESS; } int Anim::setFlag(uint16 anim_id, uint16 flag) { ANIMATION *anim; if (anim_id > _anim_count) { return FAILURE; } anim = _anim_tbl[anim_id]; if (anim == NULL) { return FAILURE; } anim->flags |= flag; return SUCCESS; } int Anim::clearFlag(uint16 anim_id, uint16 flag) { ANIMATION *anim; if (anim_id > _anim_count) { return FAILURE; } anim = _anim_tbl[anim_id]; if (anim == NULL) { return FAILURE; } anim->flags &= ~flag; return SUCCESS; } int Anim::setFrameTime(uint16 anim_id, int time) { ANIMATION *anim; if (anim_id > _anim_count) { return FAILURE; } anim = _anim_tbl[anim_id]; if (anim == NULL) { return FAILURE; } anim->frame_time = time; return SUCCESS; } int Anim::freeId(uint16 anim_id) { ANIMATION *anim; if (anim_id > _anim_count) { return FAILURE; } anim = _anim_tbl[anim_id]; if (anim == NULL) { return FAILURE; } if (GAME_GetGameType() == GID_ITE) { free(anim->frame_offsets); anim->frame_offsets = NULL; } free(anim); _anim_tbl[anim_id] = NULL; _anim_count--; return SUCCESS; } // The actual number of frames present in an animation resource is // sometimes less than number present in the .nframes member of the // animation header. For this reason, the function attempts to find // the last valid frame number, which it returns via 'n_frames' int Anim::getNumFrames(const byte *anim_resource, size_t anim_resource_len, uint16 *n_frames) { ANIMATION_HEADER ah; size_t offset; int magic; int x; if (!_initialized) { return FAILURE; } MemoryReadStream readS(anim_resource, anim_resource_len); ah.magic = readS.readUint16LE(); ah.screen_w = readS.readUint16LE(); ah.screen_h = readS.readUint16LE(); ah.unknown06 = readS.readByte(); ah.unknown07 = readS.readByte(); ah.nframes = readS.readByte(); if (GAME_GetGameType() == GID_IHNM) { *n_frames = ah.nframes; } if (ah.magic == 68) { for (x = ah.nframes; x > 0; x--) { if (getFrameOffset(anim_resource, anim_resource_len, x, &offset) != SUCCESS) { return FAILURE; } magic = *(anim_resource + offset); if (magic == SAGA_FRAME_HEADER_MAGIC) { *n_frames = x; return SUCCESS; } } return FAILURE; } return FAILURE; } int Anim::ITE_DecodeFrame(const byte *resdata, size_t resdata_len, size_t frame_offset, byte *buf, size_t buf_len) { ANIMATION_HEADER ah; FRAME_HEADER fh; byte *write_p; uint16 magic; uint16 x_start; uint16 y_start; uint32 screen_w; uint32 screen_h; int mark_byte; byte data_byte; int new_row; uint16 control_ch; uint16 param_ch; uint16 runcount; int x_vector; uint16 i; if (!_initialized) { return FAILURE; } MemoryReadStream headerReadS(resdata, resdata_len); // Read animation header ah.magic = headerReadS.readUint16LE(); ah.screen_w = headerReadS.readUint16LE(); ah.screen_h = headerReadS.readUint16LE(); ah.unknown06 = headerReadS.readByte(); ah.unknown07 = headerReadS.readByte(); ah.nframes = headerReadS.readByte(); ah.flags = headerReadS.readByte(); ah.unknown10 = headerReadS.readByte(); ah.unknown11 = headerReadS.readByte(); screen_w = ah.screen_w; screen_h = ah.screen_h; if ((screen_w * screen_h) > buf_len) { // Buffer argument is too small to hold decoded frame, abort. warning("ITE_DecodeFrame: Buffer size inadequate"); return FAILURE; } // Read frame header MemoryReadStream readS(resdata + frame_offset, resdata_len - frame_offset); // Check for frame magic byte magic = readS.readByte(); if (magic != SAGA_FRAME_HEADER_MAGIC) { warning("ITE_DecodeFrame: Invalid frame offset"); return FAILURE; } // For some strange reason, the animation header is in little // endian format, but the actual RLE encoded frame data, // including the frame header, is in big endian format. fh.x_start = readS.readUint16BE(); fh.y_start = readS.readByte(); readS.readByte(); /* Skip pad byte */ fh.x_pos = readS.readUint16BE(); fh.y_pos = readS.readUint16BE(); fh.width = readS.readUint16BE(); fh.height = readS.readUint16BE(); x_start = fh.x_start; y_start = fh.y_start; // Setup write pointer to the draw origin write_p = (buf + (y_start * screen_w) + x_start); // Begin RLE decompression to output buffer do { mark_byte = readS.readByte(); switch (mark_byte) { case 0x10: // Long Unencoded Run runcount = readS.readSint16BE(); for (i = 0; i < runcount; i++) { data_byte = readS.readByte(); if (data_byte != 0) { *write_p = data_byte; } write_p++; } continue; break; case 0x20: // Long encoded run runcount = readS.readSint16BE(); data_byte = readS.readByte(); for (i = 0; i < runcount; i++) { *write_p++ = data_byte; } continue; break; case 0x2F: // End of row x_vector = readS.readSint16BE(); new_row = readS.readByte(); // Set write pointer to the new draw origin write_p = buf + ((y_start + new_row) * screen_w) + x_start + x_vector; continue; break; case 0x30: // Reposition command x_vector = readS.readSint16BE(); write_p += x_vector; continue; break; case 0x3F: // End of frame marker return SUCCESS; break; default: break; } // Mask all but two high order control bits control_ch = mark_byte & 0xC0U; param_ch = mark_byte & 0x3FU; switch (control_ch) { case 0xC0: // 1100 0000 // Run of empty pixels runcount = param_ch + 1; write_p += runcount; continue; break; case 0x80: // 1000 0000 // Run of compressed data runcount = param_ch + 1; data_byte = readS.readByte(); for (i = 0; i < runcount; i++) { *write_p++ = data_byte; } continue; break; case 0x40: // 0100 0000 // Uncompressed run runcount = param_ch + 1; for (i = 0; i < runcount; i++) { data_byte = readS.readByte(); if (data_byte != 0) { *write_p = data_byte; } write_p++; } continue; break; default: // Unknown marker found - abort warning("ITE_DecodeFrame: Invalid RLE marker encountered"); return FAILURE; break; } } while (mark_byte != 63); // end of frame marker return SUCCESS; } int Anim::IHNM_DecodeFrame(byte *decode_buf, size_t decode_buf_len, const byte *thisf_p, size_t thisf_len, const byte **nextf_p, size_t *nextf_len) { int in_ch; int decoded_data = 0; int cont_flag = 1; int control_ch; int param_ch; byte data_pixel; int x_origin = 0; int y_origin = 0; int x_vector; int new_row; uint16 runcount; uint16 c; size_t in_ch_offset; MemoryReadStream readS(thisf_p, thisf_len); byte *outbuf_p = decode_buf; byte *outbuf_endp = (decode_buf + decode_buf_len) - 1; size_t outbuf_remain = decode_buf_len; GAME_DISPLAYINFO di; GAME_GetDisplayInfo(&di); *nextf_p = NULL; for (; cont_flag; decoded_data = 1) { in_ch_offset = readS.pos(); in_ch = readS.readByte(); switch (in_ch) { case 0x0F: // 15: Frame header { int param1; int param2; int param3; int param4; int param5; int param6; if (thisf_len - readS.pos() < 13) { warning("0x%02X: Input buffer underrun", in_ch); return FAILURE; } param1 = readS.readUint16BE(); param2 = readS.readUint16BE(); readS.readByte(); // skip 1? param3 = readS.readUint16BE(); param4 = readS.readUint16BE(); param5 = readS.readUint16BE(); param6 = readS.readUint16BE(); x_origin = param1; y_origin = param2; outbuf_p = decode_buf + x_origin + (y_origin * di.logical_w); if (outbuf_p > outbuf_endp) { warning("0x%02X: (0x%X) Invalid output position. (x: %d, y: %d)", in_ch, in_ch_offset, x_origin, y_origin); return FAILURE; } outbuf_remain = (outbuf_endp - outbuf_p) + 1; continue; } break; case 0x10: // Long Unencoded Run runcount = readS.readSint16BE(); if (thisf_len - readS.pos() < runcount) { warning("0x%02X: Input buffer underrun", in_ch); return FAILURE; } if (outbuf_remain < runcount) { warning("0x%02X: Output buffer overrun", in_ch); return FAILURE; } for (c = 0; c < runcount; c++) { data_pixel = readS.readByte(); if (data_pixel != 0) { *outbuf_p = data_pixel; } outbuf_p++; } outbuf_remain -= runcount; continue; break; case 0x1F: // 31: Unusued? if (thisf_len - readS.pos() < 3) { warning("0x%02X: Input buffer underrun", in_ch); return FAILURE; } readS.readByte(); readS.readByte(); readS.readByte(); continue; break; case 0x20: // Long compressed run if (thisf_len - readS.pos() <= 3) { warning("0x%02X: Input buffer underrun", in_ch); return FAILURE; } runcount = readS.readSint16BE(); data_pixel = readS.readByte(); for (c = 0; c < runcount; c++) { *outbuf_p++ = data_pixel; } outbuf_remain -= runcount; continue; break; case 0x2F: // End of row if (thisf_len - readS.pos() <= 4) { return FAILURE; } x_vector = readS.readSint16BE(); new_row = readS.readSint16BE(); outbuf_p = decode_buf + ((y_origin + new_row) * di.logical_w) + x_origin + x_vector; outbuf_remain = (outbuf_endp - outbuf_p) + 1; continue; break; case 0x30: // Reposition command if (thisf_len - readS.pos() < 2) { return FAILURE; } x_vector = readS.readSint16BE(); if (((x_vector > 0) && ((size_t) x_vector > outbuf_remain)) || (-x_vector > outbuf_p - decode_buf)) { warning("0x30: Invalid x_vector"); return FAILURE; } outbuf_p += x_vector; outbuf_remain -= x_vector; continue; break; case 0x3F: // 68: Frame end marker debug(1, "0x3F: Frame end marker"); if (decoded_data && (thisf_len - readS.pos() > 0)) { *nextf_p = thisf_p + readS.pos(); *nextf_len = thisf_len - readS.pos(); } else { *nextf_p = NULL; *nextf_len = 0; } cont_flag = 0; continue; break; default: break; } control_ch = in_ch & 0xC0; param_ch = in_ch & 0x3f; switch (control_ch) { case 0xC0: // Run of empty pixels runcount = param_ch + 1; if (outbuf_remain < runcount) { return FAILURE; } outbuf_p += runcount; outbuf_remain -= runcount; continue; break; case 0x80: // Run of compressed data runcount = param_ch + 1; if ((outbuf_remain < runcount) || (thisf_len - readS.pos() <= 1)) { return FAILURE; } data_pixel = readS.readByte(); for (c = 0; c < runcount; c++) { *outbuf_p++ = data_pixel; } outbuf_remain -= runcount; continue; break; case 0x40: // Uncompressed run runcount = param_ch + 1; if ((outbuf_remain < runcount) || (thisf_len - readS.pos() < runcount)) { return FAILURE; } for (c = 0; c < runcount; c++) { data_pixel = readS.readByte(); if (data_pixel != 0) { *outbuf_p = data_pixel; } outbuf_p++; } outbuf_remain -= runcount; continue; break; default: break; } } return SUCCESS; } int Anim::getFrameOffset(const byte *resdata, size_t resdata_len, uint16 find_frame, size_t *frame_offset_p) { ANIMATION_HEADER ah; uint16 num_frames; uint16 current_frame; byte mark_byte; uint16 control; uint16 runcount; uint16 magic; int i; if (!_initialized) { return FAILURE; } MemoryReadStream readS(resdata, resdata_len); // Read animation header ah.magic = readS.readUint16LE(); ah.screen_w = readS.readUint16LE(); ah.screen_h = readS.readUint16LE(); ah.unknown06 = readS.readByte(); ah.unknown07 = readS.readByte(); ah.nframes = readS.readByte(); ah.flags = readS.readByte(); ah.unknown10 = readS.readByte(); ah.unknown11 = readS.readByte(); num_frames = ah.nframes; if ((find_frame < 1) || (find_frame > num_frames)) { return FAILURE; } for (current_frame = 1; current_frame < find_frame; current_frame++) { magic = readS.readByte(); if (magic != SAGA_FRAME_HEADER_MAGIC) { // Frame sync failure. Magic Number not found return FAILURE; } // skip header for (i = 0; i < SAGA_FRAME_HEADER_LEN; i++) readS.readByte(); // For some strange reason, the animation header is in little // endian format, but the actual RLE encoded frame data, // including the frame header, is in big endian format. */ do { mark_byte = readS.readByte(); switch (mark_byte) { case 0x3F: // End of frame marker continue; break; case 0x30: // Reposition command readS.readByte(); readS.readByte(); continue; break; case 0x2F: // End of row marker readS.readByte(); readS.readByte(); readS.readByte(); continue; break; case 0x20: // Long compressed run marker readS.readByte(); readS.readByte(); readS.readByte(); continue; break; case 0x10: // (16) 0001 0000 // Long Uncompressed Run runcount = readS.readSint16BE(); for (i = 0; i < runcount; i++) readS.readByte(); continue; break; default: break; } // Mask all but two high order (control) bits control = mark_byte & 0xC0; switch (control) { case 0xC0: // Run of empty pixels continue; break; case 0x80: // Run of compressed data readS.readByte(); // Skip data byte continue; break; case 0x40: // Uncompressed run runcount = (mark_byte & 0x3f) + 1; for (i = 0; i < runcount; i++) readS.readByte(); continue; break; default: // Encountered unknown RLE marker, abort return FAILURE; break; } } while (mark_byte != 63); } *frame_offset_p = readS.pos(); return SUCCESS; } void Anim::animInfo(int argc, char *argv[]) { uint16 anim_ct; uint16 i; uint16 idx; (void)(argc); (void)(argv); anim_ct = _anim_count; _vm->_console->print("There are %d animations loaded:", anim_ct); for (idx = 0, i = 0; i < anim_ct; idx++, i++) { while (_anim_tbl[idx] == NULL) { idx++; } _vm->_console->print("%02d: Frames: %u Flags: %u", i, _anim_tbl[idx]->n_frames, _anim_tbl[idx]->flags); } } static void CF_anim_info(int argc, char *argv[], void *refCon) { ((Anim *)refCon)->animInfo(argc, argv); } } // End of namespace Saga