/* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "common/scummsys.h" #include "common/system.h" #include "common/util.h" #include "common/rect.h" #include "common/savefile.h" #include "osystem_ds.h" #include "nds.h" #include "dsmain.h" #include "nds/registers_alt.h" #include "common/config-manager.h" #include "common/str.h" #include "cdaudio.h" #include "graphics/surface.h" #include "touchkeyboard.h" #include "backends/fs/ds/ds-fs-factory.h" OSystem_DS* OSystem_DS::_instance = NULL; OSystem_DS::OSystem_DS() : eventNum(0), lastPenFrame(0), queuePos(0), _mixer(NULL), _timer(NULL), _frameBufferExists(false), _disableCursorPalette(true), _graphicsEnable(true) { // eventNum = 0; // lastPenFrame = 0; // queuePos = 0; _instance = this; // _mixer = NULL; // _timer = NULL; //_frameBufferExists = false; } OSystem_DS::~OSystem_DS() { delete _mixer; delete _timer; } int OSystem_DS::timerHandler(int t) { DSTimerManager *tm = (DSTimerManager *)g_system->getTimerManager(); tm->handler(); return t; } void OSystem_DS::initBackend() { ConfMan.setInt("autosave_period", 0); ConfMan.setBool("FM_medium_quality", true); _mixer = new DSAudioMixer(this); _timer = new DSTimerManager(); DS::setTimerCallback(&OSystem_DS::timerHandler, 10); if (ConfMan.hasKey("22khzaudio", "ds") && ConfMan.getBool("22khzaudio", "ds")) { DS::startSound(22050, 8192); } else { DS::startSound(11025, 4096); } _mixer->setOutputRate(DS::getSoundFrequency()); _mixer->setReady(true); OSystem::initBackend(); } bool OSystem_DS::hasFeature(Feature f) { // consolePrintf("hasfeature\n"); return (f == kFeatureVirtualKeyboard) || (f == kFeatureCursorHasPalette); } void OSystem_DS::setFeatureState(Feature f, bool enable) { // consolePrintf("setfeature f=%d e=%d\n", f, enable); if (f == kFeatureVirtualKeyboard) DS::setKeyboardIcon(enable); } bool OSystem_DS::getFeatureState(Feature f) { // consolePrintf("getfeat\n"); if (f == kFeatureVirtualKeyboard) return DS::getKeyboardIcon(); return false; } const OSystem::GraphicsMode* OSystem_DS::getSupportedGraphicsModes() const { return s_supportedGraphicsModes; } int OSystem_DS::getDefaultGraphicsMode() const { return 0; } bool OSystem_DS::setGraphicsMode(int mode) { return true; } bool OSystem_DS::setGraphicsMode(const char *name) { consolePrintf("Set gfx mode %s\n", name); return true; } int OSystem_DS::getGraphicsMode() const { return -1; } void OSystem_DS::initSize(uint width, uint height) { // For Lost in Time, the title screen is displayed in 640x400. // In order to support this game, the screen mode is set, but // all draw calls are ignored until the game switches to 320x200. if ((width == 640) && (height == 400)) { _graphicsEnable = false; } else { _graphicsEnable = true; DS::setGameSize(width, height); } } int16 OSystem_DS::getHeight() { return 200; } int16 OSystem_DS::getWidth() { return 320; } void OSystem_DS::setPalette(const byte *colors, uint start, uint num) { // consolePrintf("Setpal %d, %d\n", start, num); for (unsigned int r = start; r < start + num; r++) { int red = *colors; int green = *(colors + 1); int blue = *(colors + 2); red >>= 3; green >>= 3; blue >>= 3; // if (r != 255) { u16 paletteValue = red | (green << 5) | (blue << 10); if (DS::getIsDisplayMode8Bit()) { BG_PALETTE[r] = paletteValue; if (!DS::getKeyboardEnable()) { BG_PALETTE_SUB[r] = paletteValue; } } _palette[r] = paletteValue; } // if (num == 255) consolePrintf("pal:%d r:%d g:%d b:%d\n", r, red, green, blue); colors += 4; } } void OSystem_DS::setCursorPalette(const byte *colors, uint start, uint num) { // consolePrintf("Cursor palette set: start: %d, cols: %d\n", start, num); for (unsigned int r = start; r < start + num; r++) { int red = *colors; int green = *(colors + 1); int blue = *(colors + 2); red >>= 3; green >>= 3; blue >>= 3; u16 paletteValue = red | (green << 5) | (blue << 10); _cursorPalette[r] = paletteValue; colors += 4; } _disableCursorPalette = false; } bool OSystem_DS::grabRawScreen(Graphics::Surface* surf) { surf->create(DS::getGameWidth(), DS::getGameHeight(), 1); // Ensure we copy using 16 bit quantities due to limitation of VRAM addressing const u16* image = (const u16 *) DS::get8BitBackBuffer(); for (int y = 0; y < DS::getGameHeight(); y++) { DC_FlushRange(image + (y << 8), DS::getGameWidth()); for (int x = 0; x < DS::getGameWidth() >> 1; x++) { *(((u16 *) (surf->pixels)) + y * (DS::getGameWidth() >> 1) + x) = image[(y << 8) + x]; } } return true; } void OSystem_DS::grabPalette(unsigned char *colors, uint start, uint num) { // consolePrintf("Grabpalette"); for (unsigned int r = start; r < start + num; r++) { *colors++ = (BG_PALETTE[r] & 0x001F) << 3; *colors++ = (BG_PALETTE[r] & 0x03E0) >> 5 << 3; *colors++ = (BG_PALETTE[r] & 0x7C00) >> 10 << 3; } } #define MISALIGNED16(ptr) (((u32) (ptr) & 1) != 0) void OSystem_DS::copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h) { //consolePrintf("Copy rect %d, %d %d, %d ", x, y, w, h); if (!_graphicsEnable) return; if (w <= 1) return; if (h < 0) return; if (!DS::getIsDisplayMode8Bit()) return; u16* bg; s32 stride; u16* bgSub = (u16 *) BG_GFX_SUB; // The DS video RAM doesn't support 8-bit writes because Nintendo wanted // to save a few pennies/euro cents on the hardware. if (_frameBufferExists) { bg = (u16 *) _framebuffer.pixels; stride = _framebuffer.pitch; } else { bg = (u16 *) DS::get8BitBackBuffer(); stride = DS::get8BitBackBufferStride(); } if (((pitch & 1) != 0) || ((w & 1) != 0) || (((int) (buf) & 1) != 0)) { // Something is misaligned, so we have to use the slow but sure method int by = 0; for (int dy = y; dy < y + h; dy++) { u8* dest = ((u8 *) (bg)) + (dy * stride) + x; u8* destSub = ((u8 *) (bgSub)) + (dy * 512) + x; u8* src = (u8 *) buf + (pitch * by); u32 dx; u32 pixelsLeft = w; if (MISALIGNED16(dest)) { // Read modify write dest--; u16 mix = *((u16 *) dest); mix = (mix & 0x00FF) | (*src++ << 8); *dest = mix; *destSub = mix; dest += 2; destSub += 2; pixelsLeft--; } // We can now assume dest is aligned u16* dest16 = (u16 *) dest; u16* destSub16 = (u16 *) destSub; for (dx = 0; dx < pixelsLeft; dx+=2) { u16 mix; mix = *src + (*(src + 1) << 8); *dest16++ = mix; *destSub16++ = mix; src += 2; } pixelsLeft -= dx; // At the end we may have one pixel left over if (pixelsLeft != 0) { u16 mix = *dest16; mix = (mix & 0x00FF) | ((*src++) << 8); *dest16 = mix; *destSub16 = mix; } by++; } // consolePrintf("Slow method used!\n"); } else { // Stuff is aligned to 16-bit boundaries, so it's safe to do DMA. u16* src = (u16 *) buf; if (DS::getKeyboardEnable()) { for (int dy = y; dy < y + h; dy++) { u16* dest = bg + (dy * (stride >> 1)) + (x >> 1); DC_FlushRange(src, w << 1); DC_FlushRange(dest, w << 1); dmaCopyHalfWords(3, src, dest, w); while (dmaBusy(3)); src += pitch >> 1; } } else { for (int dy = y; dy < y + h; dy++) { u16* dest1 = bg + (dy * (stride >> 1)) + (x >> 1); u16* dest2 = bgSub + (dy << 8) + (x >> 1); DC_FlushRange(src, w << 1); DC_FlushRange(dest1, w << 1); DC_FlushRange(dest2, w << 1); dmaCopyHalfWords(3, src, dest1, w); if ((!_frameBufferExists) || (buf == _framebuffer.pixels)) { dmaCopyHalfWords(2, src, dest2, w); } while (dmaBusy(2) || dmaBusy(3)); src += pitch >> 1; } } } // consolePrintf("Done\n"); } void OSystem_DS::updateScreen() { if ((_frameBufferExists) && (DS::getIsDisplayMode8Bit())) { _frameBufferExists = false; // Copy temp framebuffer back to screen copyRectToScreen((byte *)_framebuffer.pixels, _framebuffer.pitch, 0, 0, _framebuffer.w, _framebuffer.h); } DS::displayMode16BitFlipBuffer(); DS::doSoundCallback(); // DS::doTimerCallback(); DS::addEventsToQueue(); // Force back buffer usage for Nippon Safes, as it doesn't double buffer it's output if (DS::getControlType() == DS::CONT_NIPPON) { OSystem_DS::instance()->lockScreen(); OSystem_DS::instance()->unlockScreen(); } } void OSystem_DS::setShakePos(int shakeOffset) { DS::setShakePos(shakeOffset); } void OSystem_DS::showOverlay () { // consolePrintf("showovl\n"); DS::displayMode16Bit(); } void OSystem_DS::hideOverlay () { DS::displayMode8Bit(); } void OSystem_DS::clearOverlay () { memset((u16 *) DS::get16BitBackBuffer(), 0, 512 * 256 * 2); // consolePrintf("clearovl\n"); } void OSystem_DS::grabOverlay (OverlayColor *buf, int pitch) { // consolePrintf("grabovl\n"); } void OSystem_DS::copyRectToOverlay (const OverlayColor *buf, int pitch, int x, int y, int w, int h) { u16* bg = (u16 *) DS::get16BitBackBuffer(); const u16* src = (const u16 *) buf; // if (x + w > 256) w = 256 - x; //if (x + h > 256) h = 256 - y; // consolePrintf("Copy rect ovl %d, %d %d, %d %d\n", x, y, w, h, pitch); for (int dy = y; dy < y + h; dy++) { // Slow but save copy: for (int dx = x; dx < x + w; dx++) { *(bg + (dy * 512) + dx) = *src; //if ((*src) != 0) consolePrintf("%d,%d: %d ", dx, dy, *src); //consolePrintf("%d,", *src); src++; } src += (pitch - w); // Fast but broken copy: (why?) /* REG_IME = 0; dmaCopy(src, bg + (dy << 9) + x, w * 2); REG_IME = 1; src += pitch;*/ } // consolePrintf("Copy rect ovl done"); } int16 OSystem_DS::getOverlayHeight() { // consolePrintf("getovlheight\n"); return getHeight(); } int16 OSystem_DS::getOverlayWidth() { // consolePrintf("getovlwid\n"); return getWidth(); } bool OSystem_DS::showMouse(bool visible) { DS::setShowCursor(visible); return true; } void OSystem_DS::warpMouse(int x, int y) { } void OSystem_DS::setMouseCursor(const byte *buf, uint w, uint h, int hotspotX, int hotspotY, byte keycolor, int targetCursorScale) { if ((w > 0) && (w < 64) && (h > 0) && (h < 64)) { memcpy(_cursorImage, buf, w * h); _cursorW = w; _cursorH = h; _cursorHotX = hotspotX; _cursorHotY = hotspotY; _cursorKey = keycolor; _cursorScale = targetCursorScale; refreshCursor(); } } void OSystem_DS::refreshCursor() { DS::setCursorIcon(_cursorImage, _cursorW, _cursorH, _cursorKey, _cursorHotX, _cursorHotY); } void OSystem_DS::addEvent(Common::Event& e) { eventQueue[queuePos++] = e; } bool OSystem_DS::pollEvent(Common::Event &event) { if (lastPenFrame != DS::getMillis()) { if (eventNum == queuePos) { eventNum = 0; queuePos = 0; // Bodge - this last event seems to be processed sometimes and not others. // So we make it something harmless which won't cause any adverse effects. event.type = Common::EVENT_KEYUP; event.kbd.ascii = 0; event.kbd.keycode = Common::KEYCODE_INVALID; event.kbd.flags = 0; // consolePrintf("type: %d\n", event.type); return false; } else { event = eventQueue[eventNum++]; // consolePrintf("type: %d\n", event.type); return true; } } return false; /* if (lastPenFrame != DS::getMillis()) { if ((eventNum == 0)) { event.type = Common::EVENT_MOUSEMOVE; event.mouse = Common::Point(DS::getPenX(), DS::getPenY()); eventNum = 1; return true; } if (eventNum == 1) { eventNum = 0; lastPenFrame = DS::getMillis(); if (DS::getPenDown()) { event.type = Common::EVENT_LBUTTONDOWN; event.mouse = Common::Point(DS::getPenX(), DS::getPenY()); consolePrintf("Down %d, %d ", event.mouse.x, event.mouse.y); return true; } else if (DS::getPenReleased()) { event.type = Common::EVENT_LBUTTONUP; event.mouse = Common::Point(DS::getPenX(), DS::getPenY()); consolePrintf("Up %d, %d ", event.mouse.x, event.mouse.y); return true; } else { return false; } } }*/ return false; } uint32 OSystem_DS::getMillis() { return DS::getMillis(); } void OSystem_DS::delayMillis(uint msecs) { int st = getMillis(); DS::addEventsToQueue(); DS::CD::update(); DS::doSoundCallback(); while (st + msecs >= getMillis()) { DS::doSoundCallback(); } DS::doTimerCallback(); DS::checkSleepMode(); DS::addEventsToQueue(); } void OSystem_DS::getTimeAndDate(struct tm &t) const { time_t curTime = time(0); t = *localtime(&curTime); } FilesystemFactory *OSystem_DS::getFilesystemFactory() { return &DSFilesystemFactory::instance(); } OSystem::MutexRef OSystem_DS::createMutex(void) { return NULL; } void OSystem_DS::lockMutex(MutexRef mutex) { } void OSystem_DS::unlockMutex(MutexRef mutex) { } void OSystem_DS::deleteMutex(MutexRef mutex) { } int OSystem_DS::getOutputSampleRate() const { return DS::getSoundFrequency(); } bool OSystem_DS::openCD(int drive) { return DS::CD::checkCD(); } bool OSystem_DS::pollCD() { return DS::CD::isPlaying(); } void OSystem_DS::playCD(int track, int num_loops, int start_frame, int duration) { DS::CD::playTrack(track, num_loops, start_frame, duration); } void OSystem_DS::stopCD() { DS::CD::stopTrack(); } void OSystem_DS::updateCD() { } void OSystem_DS::quit() { /* consolePrintf("Soft resetting..."); IPC->reset = 1; REG_IE = 0; asm("swi 0x26\n"); swiSoftReset();*/ } void OSystem_DS::setWindowCaption(const char *caption) { } void OSystem_DS::displayMessageOnOSD(const char *msg) { } Common::SaveFileManager* OSystem_DS::getSavefileManager() { bool forceSram; if (ConfMan.hasKey("forcesramsave", "ds")) { forceSram = ConfMan.getBool("forcesramsave", "ds"); } else { forceSram = false; } if (forceSram) { consolePrintf("Using SRAM save method!\n"); } if (DS::isGBAMPAvailable() && (!forceSram)) { return &mpSaveManager; } else { #ifdef GBA_SRAM_SAVE return &saveManager; #else return NULL; #endif } } Graphics::Surface* OSystem_DS::createTempFrameBuffer() { // Ensure we copy using 16 bit quantities due to limitation of VRAM addressing // If the scaler is enabled, we can just return the 8 bit back buffer, since it's in system memory // memory anyway. Otherwise, we need to copy the back buffer into the memory normally used by the scaler buffer and // then return it. // We must make sure that once the frame buffer is created, future calls to copyRectToScreen() copy to this buffer if (DS::isCpuScalerEnabled()) { _framebuffer.pixels = DS::getScalerBuffer(); _framebuffer.w = DS::getGameWidth(); _framebuffer.h = DS::getGameHeight(); _framebuffer.pitch = DS::getGameWidth(); _framebuffer.bytesPerPixel = 1; } else { s32 height = DS::getGameHeight(); s32 width = DS::getGameWidth(); s32 stride = DS::get8BitBackBufferStride(); u16* src = DS::get8BitBackBuffer(); u16* dest = DS::getScalerBuffer(); for (int y = 0; y < height; y++) { u16* destLine = dest + (y * (width / 2)); u16* srcLine = src + (y * (stride / 2)); DC_FlushRange(srcLine, width); dmaCopyHalfWords(3, srcLine, destLine, width); } _framebuffer.pixels = dest; _framebuffer.w = width; _framebuffer.h = height; _framebuffer.pitch = width; _framebuffer.bytesPerPixel = 1; } _frameBufferExists = true; /* size_t imageStrideInBytes = DS::get8BitBackBufferStride(); size_t imageStrideInWords = imageStrideInBytes / 2; u16* image = (u16 *) DS::get8BitBackBuffer(); for (int y = 0; y < DS::getGameHeight(); y++) { DC_FlushRange(image + (y * imageStrideInWords), DS::getGameWidth()); for (int x = 0; x < DS::getGameWidth() >> 1; x++) { *(((u16 *) (_framebuffer.pixels)) + y * (DS::getGameWidth() >> 1) + x) = image[(y * imageStrideInWords) + x]; // *(((u16 *) (surf->pixels)) + y * (DS::getGameWidth() >> 1) + x) = image[y * imageStrideInWords + x]; } }*/ return &_framebuffer; } Graphics::Surface *OSystem_DS::lockScreen() { if (!_frameBufferExists) { createTempFrameBuffer(); } return &_framebuffer; } void OSystem_DS::unlockScreen() { // No need to do anything here. The screen will be updated in updateScreen(). } void OSystem_DS::setFocusRectangle(const Common::Rect& rect) { DS::setTalkPos(rect.left + rect.width() / 2, rect.top + rect.height() / 2); } void OSystem_DS::clearFocusRectangle() { } void OSystem_DS::addAutoComplete(const char *word) { DS::addAutoComplete((char *) word); } void OSystem_DS::clearAutoComplete() { DS::clearAutoComplete(); } void OSystem_DS::setCharactersEntered(int count) { DS::setCharactersEntered(count); } OSystem *OSystem_DS_create() { return new OSystem_DS(); }