Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
TARGET := scummvm
APP_TITLE := ScummVM
APP_DESCRIPTION := Point-and-click adventure game engines
APP_AUTHOR := ScummVM Team
APP_ICON := $(srcdir)/backends/platform/3ds/app/icon.png
APP_RSF := $(srcdir)/backends/platform/3ds/app/scummvm.rsf
APP_BANNER_IMAGE:= $(srcdir)/backends/platform/3ds/app/banner.png
APP_BANNER_AUDIO:= $(srcdir)/backends/platform/3ds/app/banner.wav
BANNERTOOL ?= bannertool
MAKEROM ?= makerom
.PHONY: clean_3ds dist_3ds
clean: clean_3ds
clean_3ds:
$(RM) backends/platform/3ds/shader.shbin
$(RM) backends/platform/3ds/shader_shbin.h
$(RM) $(TARGET).smdh
$(RM) $(TARGET).3dsx
$(RM) $(TARGET).bnr
$(RM) $(TARGET).cia
$(RM) -rf romfs
$(RM) -rf dist_3ds
romfs: $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_NETWORKING) $(DIST_FILES_VKEYBD) $(DIST_3DS_EXTRA_FILES) $(PLUGINS)
@rm -rf romfs
@mkdir -p romfs
@cp $(DIST_FILES_THEMES) romfs/
ifdef DIST_FILES_ENGINEDATA
@cp $(DIST_FILES_ENGINEDATA) romfs/
endif
ifdef DIST_FILES_NETWORKING
@cp $(DIST_FILES_NETWORKING) romfs/
endif
ifdef DIST_FILES_VKEYBD
@cp $(DIST_FILES_VKEYBD) romfs/
endif
ifdef DIST_3DS_EXTRA_FILES
@cp -a $(DIST_3DS_EXTRA_FILES) romfs/
endif
ifeq ($(DYNAMIC_MODULES),1)
@mkdir -p romfs/plugins
@for i in $(PLUGINS); do $(STRIP) --strip-debug $$i -o romfs/plugins/`basename $$i`; done
endif
$(TARGET).smdh: $(APP_ICON)
@$(DEVKITPRO)/tools/bin/smdhtool --create "$(APP_TITLE)" "$(APP_DESCRIPTION)" "$(APP_AUTHOR)" $(APP_ICON) $@
@echo built ... $(notdir $@)
$(TARGET).3dsx: $(EXECUTABLE) $(TARGET).smdh romfs
@$(DEVKITPRO)/tools/bin/3dsxtool $< $@ --smdh=$(TARGET).smdh --romfs=romfs
@echo built ... $(notdir $@)
$(TARGET).bnr: $(APP_BANNER_IMAGE) $(APP_BANNER_AUDIO)
@$(BANNERTOOL) makebanner -o $@ -i $(APP_BANNER_IMAGE) -a $(APP_BANNER_AUDIO)
@echo built ... $(notdir $@)
$(TARGET).cia: $(EXECUTABLE) $(APP_RSF) $(TARGET).smdh $(TARGET).bnr romfs
@$(MAKEROM) -ver $(shell echo $$((($(VER_MAJOR)-2000)*256+$(VER_MINOR)*16+$(VER_PATCH)))) -f cia -target t -exefslogo -o $@ -elf $(EXECUTABLE) -rsf $(APP_RSF) -banner $(TARGET).bnr -icon $(TARGET).smdh -DAPP_ROMFS=romfs/
@echo built ... $(notdir $@)
dist_3ds: $(TARGET).cia $(TARGET).3dsx $(DIST_FILES_DOCS)
@rm -rf dist_3ds
@mkdir -p dist_3ds
@cp $(TARGET).3dsx $(TARGET).cia dist_3ds/
@cp $(DIST_FILES_DOCS) dist_3ds/
@cp $(srcdir)/backends/platform/3ds/README.md dist_3ds/README-3DS.md
@echo built ... $(notdir $@)
#---------------------------------------------------------------------------------
# rules for assembling GPU shaders
#---------------------------------------------------------------------------------
define shader-as
$(eval FILEPATH := $(patsubst %.shbin.o,%.shbin,$@))
$(eval FILE := $(patsubst %.shbin.o,%.shbin,$(notdir $@)))
$(DEVKITPRO)/tools/bin/picasso -o $(FILEPATH) $1
$(DEVKITPRO)/tools/bin/bin2s $(FILEPATH) | $(AS) -o $@
echo "extern const u8" `(echo $(FILE) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(FILEPATH) | tr . _)`.h
echo "extern const u8" `(echo $(FILE) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(FILEPATH) | tr . _)`.h
echo "extern const u32" `(echo $(FILE) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(FILEPATH) | tr . _)`.h
endef
vpath %.v.pica $(srcdir)
vpath %.g.pica $(srcdir)
vpath %.shlist $(srcdir)
%.shbin.o : %.v.pica %.g.pica
@echo $(notdir $^)
@$(call shader-as,$^)
%.shbin.o : %.v.pica
@echo $(notdir $<)
@$(call shader-as,$<)
%.shbin.o : %.shlist
@echo $(notdir $<)
@$(call shader-as,$(foreach file,$(shell cat $<),$(dir $<)/$(file)))
# osystem-graphics.cpp includes shader_shbin.h that is generated by the shader assembler
backends/platform/3ds/osystem-graphics.o: backends/platform/3ds/shader.shbin.o

View File

@@ -0,0 +1,259 @@
ScummVM 3DS README
------------------------------------------------------------------------
Table of Contents:
------------------
[1.0) Installation](#10-installation)
* [1.1 3DSX installation](#11-3dsx-installation)
* [1.2 CIA installation](#12-cia-installation)
[2.0) Controls](#20-controls)
* [2.1 Default key mappings](#21-default-key-mappings)
* [2.2 Hover mode](#22-hover-mode)
* [2.3 Drag mode](#23-drag-mode)
* [2.4 Magnify mode](#24-magnify-mode)
[3.0) Supported Games](#30-supported-games)
[4.0) Compiling](#40-compiling)
* [4.1 Prerequisites](#41-prerequisites)
* * [4.1.1 Compiling third-party libraries](#411-compiling-third-party-libraries)
* * [4.1.2 Manually setting up the environment](#412-manually-setting-up-the-environment)
* [4.2 Compiling ScummVM](#42-compiling-scummvm)
1.0) Installation
-----------------
There are two possible formats to be used: 3DSX and CIA.
The 3DSX format is considered more ethical because it does not make use of
invalid title IDs, which get logged. It is also compatible with homebrew loading
methods that do not involve CFW.
The 3DSX format is exclusively used by the Homebrew Launcher and its derivatives.
The CIA format can be installed directly to the 3DS home menu and can be launched
using any CFW (Custom Firmware) of your choice.
Installing the Homebrew Launcher or any CFW is beyond the scope of this README.
1.1) 3DSX installation
----------------
You need to merely extract the ScummVM 3DSX files to your SD card so that all
files reside in the `/3ds/scummvm/` directory. It can then be launched by Homebrew Launcher
or a similar implementation.
1.2) CIA installation
---------------------
The CIA format requires a DSP binary dump saved on your SD card as `/3ds/dspfirm.cdc`
for proper audio support. You can search online to find software to dump this.
Not having this file will cause many problems with games that need audio, sometimes
even crashing, so this is NOT considered optional.
Using any CIA installation software (search elsewhere for that), you need to install
the `scummvm.cia` file.
2.0) Controls
-------------
2.1) Default key mappings
-------------------------
The key mappings can be customized in the options dialog for the global mappings,
and in the edit game dialog for per-game mappings. Per-game mappings overlay the
global mappings, so if a button is bound to an action twice, the per-game mapping
wins.
The default keymap is:
| Buttons | Function |
|------------|--------------------------------|
| A | Left-click |
| B | Right-click |
| X | . (skips the current line) |
| Y | ESC (skips cutscenes and such) |
| L | Toggle magnify mode on/off |
| R | Toggle hover/drag modes |
| Start | Open global main menu |
| Select | Use virtual keyboard |
| Circle Pad | Move the cursor |
2.2) Hover mode
---------------
When you use the touchscreen, you are simulating the mere moving of the mouse. You
can click only with taps, meaning it is impossible to drag stuff or hold down a
mouse button without using buttons mapped to right/left-click.
2.3) Drag mode
--------------
Every time you touch and release the touchscreen, you are simulating the click and
release of the mouse buttons. At the moment, this is only a left-click.
2.4) Magnify mode
-----------------
Due to the low resolutions of the 3DS's two screens (400x240 for the top, and 320x240
for the bottom), games that run at a higher resolution will inevitably lose some visual
detail from being scaled down. This can result in situations where essential information
is undiscernable, such as text. Magnify mode increases the scale factor of the top screen
back to 1; the bottom screen remains unchanged. The touchscreen can then be used to change
which part of the game display is being magnified. This can all be done even in situations
where the cursor is disabled, such as during full-motion video (FMV) segments.
When activating magnify mode, touchscreen controls are automatically switched to hover
mode; this is to reduce the risk of the user accidentally inputting a click when changing
the magnified area via dragging the stylus. Clicking can still be done at will as in normal
hover mode. Turning off magnify mode will revert controls back to what was being used
previously (ex: if drag mode was in use prior to activating magnify mode, drag mode will
be reactivated upon exiting magnify mode), as well as restore the top screen's previous
scale factor.
Currently magnify mode can only be used when the following conditions are met:
- In the Backend tab in the options dialog, "Use Screen" is set to "Both"
- A game is currently being played
- The horizontal and/or vertical resolution in-game is greater than that of the top screen
Magnify mode cannot be used in the Launcher menu.
3.0) Supported Games
--------------------
The full game engine compatibility list can be found here:
https://scummvm.org/compatibility/
While all the above games should run on the 3DS (report if they do not), there are
many games which are unplayable due to the lack of CPU speed on the 3DS. So if
you play any games that run really slow, this is not considered a bug, but rather
a hardware limitation. Though possible GPU optimizations are always in the works.
The New 3DS console has much better performance, but there are still many newer and
high-resolution games that cannot be played. A list of these unplayable games and
game engines will eventually be listed here.
4.0) Compiling
--------------
4.1) Prerequisites
------------------
- Latest version of devkitPro, which comes with devkitARM and `libctru`
- `citro3d` thorugh devkitPro's pacman
- Optional: You should compile third-party libraries for the 3ds (commonly referred
to as portlibs in the devkitPRO community). Some games requires these to operate
properly.
4.1.1) Compiling third-party libraries
--------------------------------------
It is strongly recommended that you use devkitPro's pacman in order to get the most recent
portlibs for your build. Instructions for accessing these binaries can be found here:
https://devkitpro.org/wiki/devkitPro_pacman
The following libraries can be downloaded with pacman:
| Library | Package |
----------------|------------------------
| zlib | 3ds-zlib |
| libpng | 3ds-libpng |
| libjpeg | 3ds-libjpeg-turbo |
| freetype2 | 3ds-freetype |
| libmad | 3ds-libmad |
| libogg | 3ds-libogg |
| tremor | 3ds-libvorbisidec |
| flac | 3ds-flac |
| curl | 3ds-curl |
| libtheora | 3ds-libtheora |
At the moment of writing, the version of `freetype2` packaged by devkitPro has an issue
where it allocates too much data on the stack when ScummVM loads GUI themes.
As a workaround, an older version can be used. Version 2.6.5 is known to work well. The
instructions below can be used to compile it.
At the moment of writing, `faad` is not in the devkitPro 3DS pacman repository. It
can be compiled by following the instructions in the section below, in case it cannot
be found through pacman.
The following pacman packages are also recommended:
- `3ds-dev`
- `dkp-toolchain-vars`
Once you have the `dkp-toolchain-vars` package, you should be able to find the following
scripts in your `/opt/devkitpro` folder:
- `devkitarm.sh`
- `3dsvars.sh`
Run them one after the other with `source` in order to setup your environment variables
for cross-compiling:
```
$ source /opt/devkitpro/devkitarm.sh
$ source /opt/devkitpro/3dsvars.sh
```
After that, you can download the libraries you want to cross compile, run any autoconf
scripts that they may have, and then they can usually be built with the following steps
from their source directory:
```
$ mkdir -p $PORTLIBS
$ ./configure --prefix=$PORTLIBS --host=arm-none-eabi --disable-shared \
--enable-static
$ make
$ make install
```
Most libraries used can be compiled with same commands and configuration flags.
4.1.2) Manually setting up the environment
------------------------------------------
In case you don't have the `dkp-toolchain-vars` package downloaded, you can use the
following to set-up your environment variables.
It is assumed that you have these variables already set up. If not, then do so:
- DEVKITPRO Your root devkitPro directory (usually /opt/devkitpro)
- DEVKITARM Your root devkitARM directory (probably same as $DEVKITPRO/devkitARM)
- CTRULIB Your root libctru directory (probably same as $DEVKITPRO/libctru)
In the source directory of the library:
```
$ export PORTLIBS=$DEVKITPRO/portlibs/3ds
$ export PATH=$DEVKITPRO/tools/bin:$PORTLIBS/bin:$DEVKITARM/bin:$PATH
$ export PKG_CONFIG_PATH=$PORTLIBS/lib/pkgconfig
$ export PKG_CONFIG_LIBDIR=$PORTLIBS/lib/pkgconfig
$ export CFLAGS="-g -march=armv6k -mtune=mpcore -mfloat-abi=hard -O2
-mword-relocations -ffunction-sections -fdata-sections"
$ export CXXFLAGS="$CFLAGS"
$ export CPPFLAGS="-D_3DS -D__3DS__ -I$PORTLIBS/include -I$CTRULIB/include"
$ export LDFLAGS="-L$PORTLIBS/lib -L$CTRULIB/lib"
$ export TOOL_PREFIX=arm-none-eabi-
$ export CC=${TOOL_PREFIX}gcc
$ export CXX=${TOOL_PREFIX}g++
$ export AR=${TOOL_PREFIX}gcc-ar
$ export RANLIB=${TOOL_PREFIX}gcc-ranlib
$ export LIBS="-lctru"
```
4.2) Compiling ScummVM
----------------------
Do the following in a fresh terminal.
In case you get a "compiler not found" message, add the toolchain's executables to your PATH:
```$ export PATH=$DEVKITARM/bin:$PATH```
Note: In more recent codebases of ScummVM, you may or may not need to set the following beforehand:
```$ export PKG_CONFIG_LIBDIR=$PORTLIBS/lib/pkgconfig```
See above for $PORTLIBS.
ScummVM doesn't provide the CA certificates bundle required by the cloud synchronization features.
You need to download it from the curl website: https://curl.haxx.se/ca/cacert.pem, and instruct
the build system to package it in the binary:
```$ export DIST_3DS_EXTRA_FILES=/path/to/cacert.pem```
The name of the file must be `cacert.pem`.
From the root of the scummvm repository:
```
$ ./configure --host=3ds --enable-plugins --default-dynamic
$ make
```
Additionally compile to specific formats to be used on the 3DS:
```
$ make scummvm.3dsx
$ make scummvm.cia
```
Assuming everything was successful, you'll be able to find the binary
files in the root of your scummvm folder.
Note: for the CIA format, you will need the 'makerom' and 'bannertool' tools which are
not supplied with devkitPro.
Note: using dynamic plugins as suggested is required when building with most or all of the
game engines enabled in order to keep the memory usage low and avoid stability issues.

View File

@@ -0,0 +1,83 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
*
*/
// This file largely reuses code from libctru
// https://github.com/devkitPro/libctru/blob/33a570b1c335cc38e50b7fef6bf4ed840bbd4ab2/libctru/source/system/allocateHeaps.c
#include <3ds.h>
#define HEAP_SPLIT_SIZE_CAP (24 << 20) // 24MB
#define LINEAR_HEAP_SIZE_CAP (32 << 20) // 32MB
// Hack to get the hardware's FCRAM layout ID. It just werks.
// Layouts 0 through 5 are used exclusively in Old 3DS systems.
// Layouts 6 through 8 are used exclusively in New 3DS systems.
#define APPMEMTYPE (*(u32*)0x1FF80030)
extern "C" void __system_allocateHeaps(void) {
extern char* fake_heap_start;
extern char* fake_heap_end;
extern u32 __ctru_heap;
extern u32 __ctru_linear_heap;
extern u32 __ctru_heap_size;
extern u32 __ctru_linear_heap_size;
Result rc;
// Retrieve handle to the resource limit object for our process
Handle reslimit = 0;
rc = svcGetResourceLimit(&reslimit, CUR_PROCESS_HANDLE);
if (R_FAILED(rc))
svcBreak(USERBREAK_PANIC);
// Retrieve information about total/used memory
s64 maxCommit = 0, currentCommit = 0;
ResourceLimitType reslimitType = RESLIMIT_COMMIT;
svcGetResourceLimitLimitValues(&maxCommit, reslimit, &reslimitType, 1); // for APPLICATION this is equal to APPMEMALLOC at all times
svcGetResourceLimitCurrentValues(&currentCommit, reslimit, &reslimitType, 1);
svcCloseHandle(reslimit);
// Calculate how much remaining free memory is available
u32 remaining = (u32)(maxCommit - currentCommit) &~ 0xFFF;
__ctru_heap_size = 0;
// New 3DS needs more linear memory than Old 3DS to boot up ScummVM; app instantly crashes otherwise.
// 0x00A00000 bytes = 10 MiB, for Old 3DS
// 0x01400000 bytes = 20 MiB, for New 3DS
__ctru_linear_heap_size = APPMEMTYPE < 6 ? 0x00A00000 : 0x01400000;
__ctru_heap_size = remaining - __ctru_linear_heap_size;
// Allocate the application heap
rc = svcControlMemory(&__ctru_heap, OS_HEAP_AREA_BEGIN, 0x0, __ctru_heap_size, MEMOP_ALLOC, static_cast<MemPerm>(static_cast<int>(MEMPERM_READ) | static_cast<int>(MEMPERM_WRITE)));
if (R_FAILED(rc))
svcBreak(USERBREAK_PANIC);
// Allocate the linear heap
rc = svcControlMemory(&__ctru_linear_heap, 0x0, 0x0, __ctru_linear_heap_size, MEMOP_ALLOC_LINEAR, static_cast<MemPerm>(static_cast<int>(MEMPERM_READ) | static_cast<int>(MEMPERM_WRITE)));
if (R_FAILED(rc))
svcBreak(USERBREAK_PANIC);
// Mappable allocator init
mappableInit(OS_MAP_AREA_BEGIN, OS_MAP_AREA_END);
// Set up newlib heap
fake_heap_start = (char*)__ctru_heap;
fake_heap_end = fake_heap_start + __ctru_heap_size;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,223 @@
BasicInfo:
Title : ScummVM
ProductCode : ScummVM
Logo : Nintendo # Nintendo / Licensed / Distributed / iQue / iQueForSystem
TitleInfo:
Category : Application
UniqueId : 0xFF321
RomFs:
# Specifies the root path of the read only file system to include in the ROM.
RootPath : $(APP_ROMFS)
Option:
UseOnSD : true # true if App is to be installed to SD
FreeProductCode : true # Removes limitations on ProductCode
MediaFootPadding : false # If true CCI files are created with padding
EnableCrypt : false # Enables encryption for NCCH and CIA
EnableCompress : false # Compresses where applicable (currently only exefs:/.code)
AccessControlInfo:
CoreVersion : 2
# Exheader Format Version
DescVersion : 2
# Minimum Required Kernel Version (below is for 4.5.0)
ReleaseKernelMajor : "02"
ReleaseKernelMinor : "33"
# ExtData
UseExtSaveData : false # enables ExtData
#ExtSaveDataId : 0x300 # only set this when the ID is different to the UniqueId
# FS:USER Archive Access Permissions
# Uncomment as required
FileSystemAccess:
#- CategorySystemApplication
#- CategoryHardwareCheck
#- CategoryFileSystemTool
#- Debug
#- TwlCardBackup
#- TwlNandData
#- Boss
- DirectSdmc
#- Core
#- CtrNandRo
#- CtrNandRw
#- CtrNandRoWrite
#- CategorySystemSettings
#- CardBoard
#- ExportImportIvs
#- DirectSdmcWrite
#- SwitchCleanup
#- SaveDataMove
#- Shop
#- Shell
#- CategoryHomeMenu
# Process Settings
MemoryType : Application # Application/System/Base
SystemMode : 64MB # 64MB(Default)/96MB/80MB/72MB/32MB
IdealProcessor : 0
AffinityMask : 1
Priority : 16
MaxCpu : 0 # Let system decide
HandleTableSize : 0x200
DisableDebug : false
EnableForceDebug : false
CanWriteSharedPage : true
CanUsePrivilegedPriority : false
CanUseNonAlphabetAndNumber : true
PermitMainFunctionArgument : true
CanShareDeviceMemory : true
RunnableOnSleep : false
SpecialMemoryArrange : true
# New3DS Exclusive Process Settings
SystemModeExt : 124MB # Legacy(Default)/124MB/178MB Legacy:Use Old3DS SystemMode
CpuSpeed : 804MHz # 268MHz(Default)/804MHz
EnableL2Cache : true # false(default)/true
CanAccessCore2 : true
# Virtual Address Mappings
IORegisterMapping:
- 1ff00000-1ff7ffff # DSP memory
MemoryMapping:
- 1f000000-1f5fffff:r # VRAM
# Accessible SVCs, <Name>:<ID>
SystemCallAccess:
ArbitrateAddress: 34
Break: 60
CancelTimer: 28
ClearEvent: 25
ClearTimer: 29
CloseHandle: 35
ConnectToPort: 45
ControlMemory: 1
CreateAddressArbiter: 33
CreateEvent: 23
CreateMemoryBlock: 30
CreateMutex: 19
CreateSemaphore: 21
CreateThread: 8
CreateTimer: 26
DuplicateHandle: 39
ExitProcess: 3
ExitThread: 9
GetCurrentProcessorNumber: 17
GetHandleInfo: 41
GetProcessId: 53
GetProcessIdOfThread: 54
GetProcessIdealProcessor: 6
GetProcessInfo: 43
GetResourceLimit: 56
GetResourceLimitCurrentValues: 58
GetResourceLimitLimitValues: 57
GetSystemInfo: 42
GetSystemTick: 40
GetThreadContext: 59
GetThreadId: 55
GetThreadIdealProcessor: 15
GetThreadInfo: 44
GetThreadPriority: 11
MapMemoryBlock: 31
OutputDebugString: 61
QueryMemory: 2
ReleaseMutex: 20
ReleaseSemaphore: 22
SendSyncRequest1: 46
SendSyncRequest2: 47
SendSyncRequest3: 48
SendSyncRequest4: 49
SendSyncRequest: 50
SetThreadPriority: 12
SetTimer: 27
SignalEvent: 24
SleepThread: 10
UnmapMemoryBlock: 32
WaitSynchronization1: 36
WaitSynchronizationN: 37
Backdoor: 123
# Service List
# Maximum 34 services (32 if firmware is prior to 9.3.0)
ServiceAccessControl:
- cfg:u
- fs:USER
- gsp::Gpu
- hid:USER
- ndm:u
- pxi:dev
- APT:U
- ac:u
- act:u
- am:net
- boss:U
- cam:u
- cecd:u
- dsp::DSP
- frd:u
- http:C
- ir:USER
- ir:u
- ir:rst
- ldr:ro
- mic:u
- news:u
- nim:aoc
- nwm::UDS
- ptm:u
- qtm:u
- soc:U
- ssl:C
- y2r:u
- gsp::Lcd
SystemControlInfo:
SaveDataSize: 0K
RemasterVersion: 0
StackSize: 0x40000
# Modules that run services listed above should be included below
# Maximum 48 dependencies
# If a module is listed that isn't present on the 3DS, the title will get stuck at the logo (3ds waves)
# So act, nfc and qtm are commented for 4.x support. Uncomment if you need these.
# <module name>:<module titleid>
Dependency:
ac: 0x0004013000002402
#act: 0x0004013000003802
am: 0x0004013000001502
boss: 0x0004013000003402
camera: 0x0004013000001602
cecd: 0x0004013000002602
cfg: 0x0004013000001702
codec: 0x0004013000001802
csnd: 0x0004013000002702
dlp: 0x0004013000002802
dsp: 0x0004013000001a02
friends: 0x0004013000003202
gpio: 0x0004013000001b02
gsp: 0x0004013000001c02
hid: 0x0004013000001d02
http: 0x0004013000002902
i2c: 0x0004013000001e02
ir: 0x0004013000003302
mcu: 0x0004013000001f02
mic: 0x0004013000002002
ndm: 0x0004013000002b02
news: 0x0004013000003502
#nfc: 0x0004013000004002
nim: 0x0004013000002c02
nwm: 0x0004013000002d02
pdn: 0x0004013000002102
ps: 0x0004013000003102
ptm: 0x0004013000002202
#qtm: 0x0004013020004202
ro: 0x0004013000003702
socket: 0x0004013000002e02
spi: 0x0004013000002302
ssl: 0x0004013000002f02

View File

@@ -0,0 +1,85 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/platform/3ds/osystem.h"
#include "backends/plugins/3ds/3ds-provider.h"
#include <3ds.h>
#include <malloc.h>
enum {
SYSTEM_MODEL_2DS = 3
};
// Set the size of the stack.
u32 __stacksize__ = 64 * 1024;
// Set the size of the linear heap to allow a larger application heap.
// We do this in backends/platform/3ds/allocHeapsOverride.cpp
int main(int argc, char *argv[]) {
// Initialize basic libctru stuff
cfguInit();
gfxInitDefault();
// 800px wide top screen is not available on old 2DS systems
u8 systemModel = 0;
CFGU_GetSystemModel(&systemModel);
gfxSetWide(systemModel != SYSTEM_MODEL_2DS);
romfsInit();
osSetSpeedupEnable(true);
// consoleInit(GFX_TOP, NULL);
gdbHioDevInit();
gdbHioDevRedirectStdStreams(true, true, true);
#ifdef USE_LIBCURL
const uint32 soc_sharedmem_size = 0x10000;
void *soc_sharedmem = memalign(0x1000, soc_sharedmem_size);
socInit((u32 *)soc_sharedmem, soc_sharedmem_size);
#endif
g_system = new N3DS::OSystem_3DS();
assert(g_system);
#ifdef DYNAMIC_MODULES
PluginManager::instance().addPluginProvider(new CTRPluginProvider());
#endif
int res = scummvm_main(argc, argv);
g_system->destroy();
// Turn on both screen backlights before exiting.
if (R_SUCCEEDED(gspLcdInit())) {
GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_BOTH);
gspLcdExit();
}
#ifdef USE_LIBCURL
socExit();
#endif
gdbHioDevExit();
romfsExit();
gfxExit();
cfguExit();
return res;
}

View File

@@ -0,0 +1,17 @@
MODULE := backends/platform/3ds
MODULE_OBJS := \
main.o \
allocHeapsOverride.o \
shader.shbin.o \
sprite.o \
options.o \
osystem.o \
osystem-graphics.o \
osystem-audio.o \
osystem-events.o
# We don't use rules.mk but rather manually update OBJS and MODULE_DIRS.
MODULE_OBJS := $(addprefix $(MODULE)/, $(MODULE_OBJS))
OBJS := $(MODULE_OBJS) $(OBJS)
MODULE_DIRS += $(sort $(dir $(MODULE_OBJS)))

View File

@@ -0,0 +1,163 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
#include "backends/platform/3ds/osystem.h"
#include "gui/gui-manager.h"
#include "gui/ThemeEval.h"
#include "gui/widget.h"
#include "gui/widgets/list.h"
#include "gui/widgets/popup.h"
#include "common/translation.h"
namespace N3DS {
class N3DSOptionsWidget : public GUI::OptionsContainerWidget {
public:
explicit N3DSOptionsWidget(GuiObject *boss, const Common::String &name, const Common::String &domain);
~N3DSOptionsWidget() override;
// OptionsContainerWidget API
void load() override;
bool save() override;
bool hasKeys() override;
void setEnabled(bool e) override;
private:
// OptionsContainerWidget API
void defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const override;
GUI::CheckboxWidget *_showCursorCheckbox;
GUI::CheckboxWidget *_snapToBorderCheckbox;
GUI::CheckboxWidget *_stretchToFitCheckbox;
GUI::StaticTextWidget *_screenDesc;
GUI::PopUpWidget *_screenPopUp;
bool _enabled;
};
N3DSOptionsWidget::N3DSOptionsWidget(GuiObject *boss, const Common::String &name, const Common::String &domain) :
OptionsContainerWidget(boss, name, "N3DSOptionsDialog", domain), _enabled(true) {
_showCursorCheckbox = new GUI::CheckboxWidget(widgetsBoss(), "N3DSOptionsDialog.ShowCursor", _("Show mouse cursor"), Common::U32String(), 0, 'T');
_snapToBorderCheckbox = new GUI::CheckboxWidget(widgetsBoss(), "N3DSOptionsDialog.SnapToBorder", _("Snap to edges"), Common::U32String(), 0, 'T');
_stretchToFitCheckbox = new GUI::CheckboxWidget(widgetsBoss(), "N3DSOptionsDialog.StretchToFit", _("Stretch to fit"), Common::U32String(), 0, 'T');
_screenDesc = new GUI::StaticTextWidget(widgetsBoss(), "N3DSOptionsDialog.ScreenText", _("Use Screen:"));
_screenPopUp = new GUI::PopUpWidget(widgetsBoss(), "N3DSOptionsDialog.Screen");
_screenPopUp->appendEntry(_c("Top", "3ds-screen"), kScreenTop);
_screenPopUp->appendEntry(_c("Bottom", "3ds-screen"), kScreenBottom);
_screenPopUp->appendEntry(_c("Both", "3ds-screen"), kScreenBoth);
}
N3DSOptionsWidget::~N3DSOptionsWidget() {
}
void N3DSOptionsWidget::defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const {
layouts.addDialog(layoutName, overlayedLayout)
.addLayout(GUI::ThemeLayout::kLayoutVertical)
.addPadding(0, 0, 0, 0)
.addWidget("ShowCursor", "Checkbox")
.addWidget("SnapToBorder", "Checkbox")
.addWidget("StretchToFit", "Checkbox")
.addLayout(GUI::ThemeLayout::kLayoutHorizontal)
.addPadding(16, 16, 0, 0)
.addWidget("ScreenText", "OptionsLabel")
.addWidget("Screen", "PopUp")
.closeLayout()
.closeLayout()
.closeDialog();
}
void N3DSOptionsWidget::load() {
_showCursorCheckbox->setState(ConfMan.getBool("3ds_showcursor", _domain));
_snapToBorderCheckbox->setState(ConfMan.getBool("3ds_snaptoborder", _domain));
_stretchToFitCheckbox->setState(ConfMan.getBool("3ds_stretchtofit", _domain));
_screenPopUp->setSelectedTag(ConfMan.getInt("3ds_screen", _domain));
}
bool N3DSOptionsWidget::save() {
if (_enabled) {
ConfMan.setBool("3ds_showcursor", _showCursorCheckbox->getState(), _domain);
ConfMan.setBool("3ds_snaptoborder", _snapToBorderCheckbox->getState(), _domain);
ConfMan.setBool("3ds_stretchtofit", _stretchToFitCheckbox->getState(), _domain);
ConfMan.setInt("3ds_screen", _screenPopUp->getSelectedTag(), _domain);
} else {
ConfMan.removeKey("3ds_showcursor", _domain);
ConfMan.removeKey("3ds_snaptoborder", _domain);
ConfMan.removeKey("3ds_stretchtofit", _domain);
ConfMan.removeKey("3ds_screen", _domain);
}
return true;
}
bool N3DSOptionsWidget::hasKeys() {
return ConfMan.hasKey("3ds_showcursor", _domain) ||
ConfMan.hasKey("3ds_snaptoborder", _domain) ||
ConfMan.hasKey("3ds_stretchtofit", _domain) ||
ConfMan.hasKey("3ds_screen", _domain);
}
void N3DSOptionsWidget::setEnabled(bool e) {
_enabled = e;
_showCursorCheckbox->setEnabled(e);
_snapToBorderCheckbox->setEnabled(e);
_stretchToFitCheckbox->setEnabled(e);
_screenDesc->setEnabled(e);
_screenPopUp->setEnabled(e);
}
GUI::OptionsContainerWidget *OSystem_3DS::buildBackendOptionsWidget(GUI::GuiObject *boss, const Common::String &name, const Common::String &target) const {
return new N3DSOptionsWidget(boss, name, target);
}
void OSystem_3DS::registerDefaultSettings(const Common::String &target) const {
ConfMan.registerDefault("3ds_showcursor", true);
ConfMan.registerDefault("3ds_snaptoborder", true);
ConfMan.registerDefault("3ds_stretchtofit", false);
ConfMan.registerDefault("3ds_screen", kScreenBoth);
}
void OSystem_3DS::applyBackendSettings() {
int oldScreen = _screen;
_showCursor = ConfMan.getBool("3ds_showcursor");
_snapToBorder = ConfMan.getBool("3ds_snaptoborder");
_stretchToFit = ConfMan.getBool("3ds_stretchtofit");
_screen = (Screen)ConfMan.getInt("3ds_screen");
updateBacklight();
updateConfig();
if (_screen != oldScreen) {
_screenChangeId++;
g_gui.checkScreenChange();
}
}
} // namespace N3DS

View File

@@ -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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/platform/3ds/osystem.h"
#include "audio/mixer.h"
namespace N3DS {
static bool hasAudio = false;
static void audioThreadFunc(void *arg) {
Audio::MixerImpl *mixer = (Audio::MixerImpl *)arg;
OSystem_3DS *osys = dynamic_cast<OSystem_3DS *>(g_system);
const int channel = 0;
int bufferIndex = 0;
const int bufferCount = 2;
const int sampleRate = mixer->getOutputRate();
const int bufferSamples = 1024;
const int bufferSize = bufferSamples * 4;
ndspWaveBuf buffers[bufferCount];
for (int i = 0; i < bufferCount; ++i) {
memset(&buffers[i], 0, sizeof(ndspWaveBuf));
buffers[i].data_vaddr = linearAlloc(bufferSize);
buffers[i].looping = false;
buffers[i].status = NDSP_WBUF_FREE;
}
ndspChnReset(channel);
ndspChnSetInterp(channel, NDSP_INTERP_LINEAR);
ndspChnSetRate(channel, sampleRate);
ndspChnSetFormat(channel, NDSP_FORMAT_STEREO_PCM16);
while (!osys->exiting) {
svcSleepThread(5000 * 1000); // Wake up the thread every 5 ms
if (osys->sleeping) {
continue;
}
ndspWaveBuf *buf = &buffers[bufferIndex];
if (buf->status == NDSP_WBUF_FREE || buf->status == NDSP_WBUF_DONE) {
buf->nsamples = mixer->mixCallback(buf->data_adpcm, bufferSize);
if (buf->nsamples > 0) {
DSP_FlushDataCache(buf->data_vaddr, bufferSize);
ndspChnWaveBufAdd(channel, buf);
}
bufferIndex++;
bufferIndex %= bufferCount;
}
}
for (int i = 0; i < bufferCount; ++i)
linearFree(buffers[i].data_pcm16);
}
void OSystem_3DS::initAudio() {
_mixer = new Audio::MixerImpl(22050);
hasAudio = R_SUCCEEDED(ndspInit());
_mixer->setReady(false);
if (hasAudio) {
s32 prio = 0;
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
audioThread = threadCreate(&audioThreadFunc, _mixer, 32 * 1048, prio - 1, -2, false);
}
}
void OSystem_3DS::destroyAudio() {
if (hasAudio) {
threadJoin(audioThread, U64_MAX);
threadFree(audioThread);
ndspExit();
}
delete _mixer;
_mixer = 0;
}
Audio::Mixer *OSystem_3DS::getMixer() {
assert(_mixer);
return _mixer;
}
} // namespace N3DS

View File

@@ -0,0 +1,430 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
#include "backends/platform/3ds/osystem.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymapper-defaults.h"
#include "backends/keymapper/hardware-input.h"
#include "backends/keymapper/keymap.h"
#include "backends/keymapper/keymapper.h"
#include "backends/keymapper/standard-actions.h"
#include "backends/timer/default/default-timer.h"
#include "common/translation.h"
#include "engines/engine.h"
#include "gui/gui-manager.h"
namespace N3DS {
static Common::Mutex *eventMutex;
static InputMode inputMode = MODE_DRAG;
static InputMode savedInputMode = MODE_DRAG;
static aptHookCookie cookie;
static const Common::HardwareInputTableEntry ctrJoystickButtons[] = {
{ "JOY_A", Common::JOYSTICK_BUTTON_A, _s("A") },
{ "JOY_B", Common::JOYSTICK_BUTTON_B, _s("B") },
{ "JOY_X", Common::JOYSTICK_BUTTON_X, _s("X") },
{ "JOY_Y", Common::JOYSTICK_BUTTON_Y, _s("Y") },
{ "JOY_BACK", Common::JOYSTICK_BUTTON_BACK, _s("Select") },
{ "JOY_START", Common::JOYSTICK_BUTTON_START, _s("Start") },
{ "JOY_LEFT_STICK", Common::JOYSTICK_BUTTON_LEFT_STICK, _s("ZL") },
{ "JOY_RIGHT_STICK", Common::JOYSTICK_BUTTON_RIGHT_STICK, _s("ZR") },
{ "JOY_LEFT_SHOULDER", Common::JOYSTICK_BUTTON_LEFT_SHOULDER, _s("L") },
{ "JOY_RIGHT_SHOULDER", Common::JOYSTICK_BUTTON_RIGHT_SHOULDER, _s("R") },
{ "JOY_UP", Common::JOYSTICK_BUTTON_DPAD_UP, _s("D-pad Up") },
{ "JOY_DOWN", Common::JOYSTICK_BUTTON_DPAD_DOWN, _s("D-pad Down") },
{ "JOY_LEFT", Common::JOYSTICK_BUTTON_DPAD_LEFT, _s("D-pad Left") },
{ "JOY_RIGHT", Common::JOYSTICK_BUTTON_DPAD_RIGHT, _s("D-pad Right") },
{ nullptr, 0, nullptr }
};
static const Common::AxisTableEntry ctrJoystickAxes[] = {
{ "JOY_LEFT_STICK_X", Common::JOYSTICK_AXIS_LEFT_STICK_X, Common::kAxisTypeFull, _s("C-Pad X") },
{ "JOY_LEFT_STICK_Y", Common::JOYSTICK_AXIS_LEFT_STICK_Y, Common::kAxisTypeFull, _s("C-Pad Y") },
{ nullptr, 0, Common::kAxisTypeFull, nullptr }
};
const Common::HardwareInputTableEntry ctrMouseButtons[] = {
{ "MOUSE_LEFT", Common::MOUSE_BUTTON_LEFT, _s("Touch") },
{ nullptr, 0, nullptr }
};
static const int16 CIRCLE_MAX = 160;
static void pushEventQueue(Common::Queue<Common::Event> *queue, Common::Event &event) {
Common::StackLock lock(*eventMutex);
queue->push(event);
}
static void doJoyEvent(Common::Queue<Common::Event> *queue, u32 keysPressed, u32 keysReleased, u32 ctrKey, uint8 svmButton) {
if (keysPressed & ctrKey || keysReleased & ctrKey) {
Common::Event event;
event.type = (keysPressed & ctrKey) ? Common::EVENT_JOYBUTTON_DOWN : Common::EVENT_JOYBUTTON_UP;
event.joystick.button = svmButton;
pushEventQueue(queue, event);
}
}
static void eventThreadFunc(void *arg) {
OSystem_3DS *osys = dynamic_cast<OSystem_3DS *>(g_system);
Common::Queue<Common::Event> *eventQueue = (Common::Queue<Common::Event> *)arg;
uint32 touchStartTime = osys->getMillis();
touchPosition lastTouch = {0, 0};
circlePosition lastCircle = {0, 0};
int borderSnapZone = 6;
Common::Event event;
while (!osys->exiting) {
do {
osys->delayMillis(10);
} while (osys->sleeping && !osys->exiting);
hidScanInput();
u32 held = hidKeysHeld();
u32 keysPressed = hidKeysDown();
u32 keysReleased = hidKeysUp();
// Touch screen events
if (held & KEY_TOUCH) {
touchPosition touch;
hidTouchRead(&touch);
if (osys->_snapToBorder) {
if (touch.px < borderSnapZone) {
touch.px = 0;
}
if (touch.px > 319 - borderSnapZone) {
touch.px = 319;
}
if (touch.py < borderSnapZone) {
touch.py = 0;
}
if (touch.py > 239 - borderSnapZone) {
touch.py = 239;
}
}
osys->transformPoint(touch);
event.mouse.x = touch.px;
event.mouse.y = touch.py;
if (keysPressed & KEY_TOUCH) {
touchStartTime = osys->getMillis();
if (inputMode == MODE_DRAG) {
event.type = Common::EVENT_LBUTTONDOWN;
pushEventQueue(eventQueue, event);
}
} else if (touch.px != lastTouch.px || touch.py != lastTouch.py) {
event.type = Common::EVENT_MOUSEMOVE;
pushEventQueue(eventQueue, event);
}
lastTouch = touch;
} else if (keysReleased & KEY_TOUCH) {
event.mouse.x = lastTouch.px;
event.mouse.y = lastTouch.py;
if (inputMode == MODE_DRAG) {
event.type = Common::EVENT_LBUTTONUP;
pushEventQueue(eventQueue, event);
} else if (osys->getMillis() - touchStartTime < 200) {
// Process click in MODE_HOVER
event.type = Common::EVENT_MOUSEMOVE;
pushEventQueue(eventQueue, event);
event.type = Common::EVENT_LBUTTONDOWN;
pushEventQueue(eventQueue, event);
event.type = Common::EVENT_LBUTTONUP;
pushEventQueue(eventQueue, event);
}
}
// C-Pad events
circlePosition circle;
hidCircleRead(&circle);
if (circle.dx != lastCircle.dx) {
int32 position = (int32)circle.dx * Common::JOYAXIS_MAX / CIRCLE_MAX;
event.type = Common::EVENT_JOYAXIS_MOTION;
event.joystick.axis = Common::JOYSTICK_AXIS_LEFT_STICK_X;
event.joystick.position = CLIP<int32>(position, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
pushEventQueue(eventQueue, event);
}
if (circle.dy != lastCircle.dy) {
int32 position = -(int32)circle.dy * Common::JOYAXIS_MAX / CIRCLE_MAX;
event.type = Common::EVENT_JOYAXIS_MOTION;
event.joystick.axis = Common::JOYSTICK_AXIS_LEFT_STICK_Y;
event.joystick.position = CLIP<int32>(position, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
pushEventQueue(eventQueue, event);
}
lastCircle = circle;
// Button events
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_L, Common::JOYSTICK_BUTTON_LEFT_SHOULDER);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_R, Common::JOYSTICK_BUTTON_RIGHT_SHOULDER);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_A, Common::JOYSTICK_BUTTON_A);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_B, Common::JOYSTICK_BUTTON_B);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_X, Common::JOYSTICK_BUTTON_X);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_Y, Common::JOYSTICK_BUTTON_Y);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_DUP, Common::JOYSTICK_BUTTON_DPAD_UP);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_DDOWN, Common::JOYSTICK_BUTTON_DPAD_DOWN);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_DLEFT, Common::JOYSTICK_BUTTON_DPAD_LEFT);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_DRIGHT, Common::JOYSTICK_BUTTON_DPAD_RIGHT);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_START, Common::JOYSTICK_BUTTON_START);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_SELECT, Common::JOYSTICK_BUTTON_BACK);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_ZL, Common::JOYSTICK_BUTTON_LEFT_STICK);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_ZR, Common::JOYSTICK_BUTTON_RIGHT_STICK);
}
}
static void aptHookFunc(APT_HookType hookType, void *param) {
OSystem_3DS *osys = dynamic_cast<OSystem_3DS *>(g_system);
switch (hookType) {
case APTHOOK_ONSUSPEND:
case APTHOOK_ONSLEEP:
if (g_engine) {
osys->_sleepPauseToken = g_engine->pauseEngine();
}
osys->sleeping = true;
if (R_SUCCEEDED(gspLcdInit())) {
GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_BOTH);
gspLcdExit();
}
break;
case APTHOOK_ONRESTORE:
case APTHOOK_ONWAKEUP:
if (g_engine) {
osys->_sleepPauseToken.clear();
}
osys->sleeping = false;
osys->updateBacklight();
osys->updateConfig();
break;
case APTHOOK_ONEXIT:
break;
default:
warning("Unhandled APT hook, type: %d", hookType);
}
}
static void timerThreadFunc(void *arg) {
OSystem_3DS *osys = (OSystem_3DS *)arg;
DefaultTimerManager *tm = (DefaultTimerManager *)osys->getTimerManager();
while (!osys->exiting) {
g_system->delayMillis(10);
tm->handler();
}
}
Common::HardwareInputSet *OSystem_3DS::getHardwareInputSet() {
using namespace Common;
CompositeHardwareInputSet *inputSet = new CompositeHardwareInputSet();
// Touch input sends mouse events for now, so we need to declare we have a mouse...
inputSet->addHardwareInputSet(new MouseHardwareInputSet(ctrMouseButtons));
inputSet->addHardwareInputSet(new JoystickHardwareInputSet(ctrJoystickButtons, ctrJoystickAxes));
return inputSet;
}
void OSystem_3DS::initEvents() {
eventMutex = new Common::Mutex();
s32 prio = 0;
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
_timerThread = threadCreate(&timerThreadFunc, this, 32 * 1024, prio - 1, -2, false);
_eventThread = threadCreate(&eventThreadFunc, &_eventQueue, 32 * 1024, prio - 1, -2, false);
aptHook(&cookie, aptHookFunc, this);
_eventManager->getEventDispatcher()->registerObserver(this, 10, false);
}
void OSystem_3DS::destroyEvents() {
_eventManager->getEventDispatcher()->unregisterObserver(this);
threadJoin(_timerThread, U64_MAX);
threadFree(_timerThread);
threadJoin(_eventThread, U64_MAX);
threadFree(_eventThread);
delete eventMutex;
}
void OSystem_3DS::transformPoint(touchPosition &point) {
if (!_overlayInGUI) {
point.px = static_cast<float>(point.px) / _gameBottomTexture.getScaleX() - _gameBottomTexture.getPosX();
point.py = static_cast<float>(point.py) / _gameBottomTexture.getScaleY() - _gameBottomTexture.getPosY();
}
clipPoint(point);
}
void OSystem_3DS::clipPoint(touchPosition &point) {
if (_overlayInGUI) {
point.px = CLIP<uint16>(point.px, 0, getOverlayWidth() - 1);
point.py = CLIP<uint16>(point.py, 0, getOverlayHeight() - 1);
} else {
point.px = CLIP<uint16>(point.px, 0, _gameTopTexture.actualWidth - 1);
point.py = CLIP<uint16>(point.py, 0, _gameTopTexture.actualHeight - 1);
}
}
enum _3DSCustomEvent {
k3DSEventToggleDragMode,
k3DSEventToggleMagnifyMode
};
Common::KeymapArray OSystem_3DS::getGlobalKeymaps() {
using namespace Common;
Keymap *keymap = new Keymap(Keymap::kKeymapTypeGlobal, "3ds", "3DS");
Action *act;
act = new Action("DRAGM", _("Toggle Drag Mode"));
act->setCustomBackendActionEvent(k3DSEventToggleDragMode);
act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
keymap->addAction(act);
act = new Action("MAGM", _("Toggle Magnify Mode"));
act->setCustomBackendActionEvent(k3DSEventToggleMagnifyMode);
act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
keymap->addAction(act);
return Keymap::arrayOf(keymap);
}
Common::KeymapperDefaultBindings *OSystem_3DS::getKeymapperDefaultBindings() {
Common::KeymapperDefaultBindings *keymapperDefaultBindings = new Common::KeymapperDefaultBindings();
// Unmap the main menu standard action so LEFT_SHOULDER can be used for drag mode
keymapperDefaultBindings->setDefaultBinding("engine-default", Common::kStandardActionOpenMainMenu, "");
return keymapperDefaultBindings;
}
bool OSystem_3DS::pollEvent(Common::Event &event) {
if (!aptMainLoop()) {
// The system requested us to quit
if (_sleepPauseToken.isActive()) {
_sleepPauseToken.clear();
}
event.type = Common::EVENT_QUIT;
return true;
}
// If magnify mode is on when returning to Launcher, turn it off
if (_eventManager->shouldReturnToLauncher()) {
if (_magnifyMode == MODE_MAGON) {
_magnifyMode = MODE_MAGOFF;
updateSize();
if (savedInputMode == MODE_DRAG) {
inputMode = savedInputMode;
displayMessageOnOSD(_("Magnify Mode Off. Reactivating Drag Mode.\nReturning to Launcher..."));
} else {
displayMessageOnOSD(_("Magnify Mode Off. Returning to Launcher..."));
}
}
}
Common::StackLock lock(*eventMutex);
if (_eventQueue.empty()) {
return false;
}
event = _eventQueue.pop();
if (Common::isMouseEvent(event)) {
warpMouse(event.mouse.x, event.mouse.y);
}
return true;
}
bool OSystem_3DS::notifyEvent(const Common::Event &event) {
if (event.type != Common::EVENT_CUSTOM_BACKEND_ACTION_START
&& event.type != Common::EVENT_CUSTOM_BACKEND_ACTION_END) {
return false; // We're only interested in custom backend events
}
if (event.type == Common::EVENT_CUSTOM_BACKEND_ACTION_END) {
return true; // We'll say we have handled the event so it is not propagated
}
switch ((_3DSCustomEvent)event.customType) {
case k3DSEventToggleDragMode:
if (inputMode == MODE_DRAG) {
inputMode = savedInputMode = MODE_HOVER;
displayMessageOnOSD(_("Hover Mode"));
} else {
if (_magnifyMode == MODE_MAGOFF) {
inputMode = savedInputMode = MODE_DRAG;
displayMessageOnOSD(_("Drag Mode"));
} else {
displayMessageOnOSD(_("Cannot Switch to Drag Mode while Magnify Mode is On"));
}
}
return true;
case k3DSEventToggleMagnifyMode:
if (_overlayVisible) {
displayMessageOnOSD(_("Magnify Mode cannot be activated in menus."));
} else if (_screen != kScreenBoth && _magnifyMode == MODE_MAGOFF) {
// TODO: Automatically enable both screens while magnify mode is on
displayMessageOnOSD(_("Magnify Mode can only be activated\n when both screens are enabled."));
} else if (_gameWidth <= 400 && _gameHeight <= 240) {
displayMessageOnOSD(_("In-game resolution too small to magnify."));
} else {
if (_magnifyMode == MODE_MAGOFF) {
_magnifyMode = MODE_MAGON;
if (inputMode == MODE_DRAG) {
inputMode = MODE_HOVER;
displayMessageOnOSD(_("Magnify Mode On. Switching to Hover Mode..."));
} else {
displayMessageOnOSD(_("Magnify Mode On"));
}
} else {
_magnifyMode = MODE_MAGOFF;
updateSize();
if (savedInputMode == MODE_DRAG) {
inputMode = savedInputMode;
displayMessageOnOSD(_("Magnify Mode Off. Reactivating Drag Mode..."));
} else {
displayMessageOnOSD(_("Magnify Mode Off"));
}
}
}
return true;
}
return false;
}
} // namespace N3DS

View File

@@ -0,0 +1,887 @@
/* 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
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This _program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this _program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*/
#include "backends/platform/3ds/osystem.h"
#include "backends/platform/3ds/shader_shbin.h"
#include "common/rect.h"
#include "graphics/blit.h"
#include "graphics/fontman.h"
#include "gui/gui-manager.h"
// Used to transfer the final rendered display to the framebuffer
#define DISPLAY_TRANSFER_FLAGS \
(GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) | \
GX_TRANSFER_RAW_COPY(0) | GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | \
GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | \
GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO))
#define TEXTURE_TRANSFER_FLAGS(in, out) \
(GX_TRANSFER_FLIP_VERT(1) | GX_TRANSFER_OUT_TILED(1) | \
GX_TRANSFER_RAW_COPY(0) | GX_TRANSFER_IN_FORMAT(in) | \
GX_TRANSFER_OUT_FORMAT(out) | GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO))
#define DEFAULT_MODE _modeRGBA8
namespace N3DS {
/* Group the various enums, values, etc. needed for
* each graphics mode into instaces of GfxMode3DS */
static const GfxMode3DS _modeRGBA8 = { Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0),
GPU_RGBA8, TEXTURE_TRANSFER_FLAGS(GX_TRANSFER_FMT_RGBA8, GX_TRANSFER_FMT_RGBA8) };
static const GfxMode3DS _modeRGBX8 = { Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0),
GPU_RGB8, TEXTURE_TRANSFER_FLAGS(GX_TRANSFER_FMT_RGBA8, GX_TRANSFER_FMT_RGB8) };
static const GfxMode3DS _modeRGB565 = { Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0),
GPU_RGB565, TEXTURE_TRANSFER_FLAGS(GX_TRANSFER_FMT_RGB565, GX_TRANSFER_FMT_RGB565) };
static const GfxMode3DS _modeRGB5A1 = { Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0),
GPU_RGBA5551, TEXTURE_TRANSFER_FLAGS(GX_TRANSFER_FMT_RGB5A1, GX_TRANSFER_FMT_RGB5A1) };
static const GfxMode3DS _modeRGBA4 = { Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0),
GPU_RGBA4, TEXTURE_TRANSFER_FLAGS(GX_TRANSFER_FMT_RGBA4, GX_TRANSFER_FMT_RGBA4) };
static const GfxMode3DS _modeCLUT8 = _modeRGBX8;
static const GfxMode3DS *gfxModes[] = { &_modeRGBX8, &_modeRGB565, &_modeRGB5A1, &_modeRGBA4, &_modeCLUT8 };
void OSystem_3DS::init3DSGraphics() {
_gfxState.gfxMode = gfxModes[CLUT8];
_pfGame = Graphics::PixelFormat::createFormatCLUT8();
_pfDefaultTexture = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0);
C3D_Init(C3D_DEFAULT_CMDBUF_SIZE);
// Initialize the render targets
int topScreenWidth = gfxIsWide() ? 800 : 400;
_renderTargetTop =
C3D_RenderTargetCreate(240, topScreenWidth, GPU_RB_RGB8, -1);
C3D_RenderTargetClear(_renderTargetTop, C3D_CLEAR_ALL, 0x0000000, 0);
C3D_RenderTargetSetOutput(_renderTargetTop, GFX_TOP, GFX_LEFT,
DISPLAY_TRANSFER_FLAGS);
_renderTargetBottom =
C3D_RenderTargetCreate(240, 320, GPU_RB_RGB8, -1);
C3D_RenderTargetClear(_renderTargetBottom, C3D_CLEAR_ALL, 0x00000000, 0);
C3D_RenderTargetSetOutput(_renderTargetBottom, GFX_BOTTOM, GFX_LEFT,
DISPLAY_TRANSFER_FLAGS);
// Load and bind simple default shader (shader.v.pica)
_dvlb = DVLB_ParseFile((u32*)const_cast<u8 *>(shader_shbin), shader_shbin_size);
shaderProgramInit(&_program);
shaderProgramSetVsh(&_program, &_dvlb->DVLE[0]);
C3D_BindProgram(&_program);
_projectionLocation = shaderInstanceGetUniformLocation(_program.vertexShader, "projection");
_modelviewLocation = shaderInstanceGetUniformLocation(_program.vertexShader, "modelView");
C3D_AttrInfo *attrInfo = C3D_GetAttrInfo();
AttrInfo_Init(attrInfo);
AttrInfo_AddLoader(attrInfo, 0, GPU_FLOAT, 3); // v0=position
AttrInfo_AddLoader(attrInfo, 1, GPU_FLOAT, 2); // v1=texcoord
Mtx_OrthoTilt(&_projectionTop, 0.0, 400.0, 240.0, 0.0, 0.0, 1.0, true);
Mtx_OrthoTilt(&_projectionBottom, 0.0, 320.0, 240.0, 0.0, 0.0, 1.0, true);
C3D_TexEnv *env = C3D_GetTexEnv(0);
C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR);
C3D_TexEnvOpRgb(env, GPU_TEVOP_RGB_SRC_COLOR, GPU_TEVOP_RGB_SRC_COLOR, GPU_TEVOP_RGB_SRC_COLOR);
C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE);
C3D_DepthTest(false, GPU_GEQUAL, GPU_WRITE_ALL);
C3D_CullFace(GPU_CULL_NONE);
// _overlay initialized in updateSize()
}
void OSystem_3DS::destroy3DSGraphics() {
_gameScreen.free();
_cursor.free();
shaderProgramFree(&_program);
DVLB_Free(_dvlb);
C3D_RenderTargetDelete(_renderTargetTop);
C3D_RenderTargetDelete(_renderTargetBottom);
C3D_Fini();
}
bool OSystem_3DS::hasFeature(OSystem::Feature f) {
return (f == OSystem::kFeatureCursorPalette ||
f == OSystem::kFeatureCursorAlpha ||
f == OSystem::kFeatureFilteringMode ||
f == OSystem::kFeatureOverlaySupportsAlpha ||
f == OSystem::kFeatureKbdMouseSpeed ||
f == OSystem::kFeatureJoystickDeadzone);
}
void OSystem_3DS::setFeatureState(OSystem::Feature f, bool enable) {
switch (f) {
case OSystem::kFeatureCursorPalette:
_cursorPaletteEnabled = enable;
flushCursor();
break;
case OSystem::kFeatureFilteringMode:
_filteringEnabled = enable;
break;
default:
break;
}
}
bool OSystem_3DS::getFeatureState(OSystem::Feature f) {
switch (f) {
case OSystem::kFeatureCursorPalette:
return _cursorPaletteEnabled;
case OSystem::kFeatureFilteringMode:
return _filteringEnabled;
default:
return false;
}
}
GraphicsModeID OSystem_3DS::chooseMode(Graphics::PixelFormat *format) {
if (format->bytesPerPixel > 2) {
return RGBA8;
} else if (format->bytesPerPixel > 1) {
if (format->aBits() > 1) {
return RGBA4;
} else if (format->gBits() > 5) {
return RGB565;
} else {
return RGB5A1;
}
}
return CLUT8;
}
bool OSystem_3DS::setGraphicsMode(GraphicsModeID modeID) {
switch (modeID) {
case RGBA8:
case RGB565:
case RGB5A1:
case RGBA4:
case CLUT8:
_gfxState.gfxMode = gfxModes[modeID];
return true;
default:
return false;
}
}
void OSystem_3DS::initSize(uint width, uint height,
const Graphics::PixelFormat *format) {
debug("3ds initsize w:%d h:%d", width, height);
updateBacklight();
updateConfig();
_gameWidth = width;
_gameHeight = height;
_magCenterX = _magWidth / 2;
_magCenterY = _magHeight / 2;
_oldPfGame = _pfGame;
if (!format) {
_pfGame = Graphics::PixelFormat::createFormatCLUT8();
} else {
debug("pixelformat: %d %d %d %d %d", format->bytesPerPixel, format->rBits(), format->gBits(), format->bBits(), format->aBits());
_pfGame = *format;
}
/* If the current graphics mode does not fit with the pixel
* format being requested, choose one that does and switch to it */
assert(_pfGame.bytesPerPixel > 0);
if (_pfGame != _oldPfGame) {
assert(_transactionState == kTransactionActive);
_gfxState.gfxModeID = chooseMode(&_pfGame);
_transactionDetails.formatChanged = true;
}
_gameTopTexture.create(width, height, _gfxState.gfxMode, true);
if (_pfGame == _gameTopTexture.format)
_gameScreen.free();
else
_gameScreen.create(width, height, _pfGame);
_focusDirty = true;
_focusRect = Common::Rect(_gameWidth, _gameHeight);
updateSize();
}
void OSystem_3DS::updateSize() {
// Initialize _overlay here so that it can be reinitialized when _screen is changed.
// Overlay sprite must have a width matching or exceeding that of the screen to
// which it's set to render, otherwise portions of the screen will not render.
// _screen == kScreenTop
// >>> overlay renders to top screen
// >>> top screen is 400 pixels wide
// _screen == (kScreenBottom | kScreenBoth)
// >>> overlay renders to bottom screen
// >>> bottom screen is 320 pixels wide
_overlay.create(_screen == kScreenTop ? 400 : 320, 240, &DEFAULT_MODE, true);
if (_stretchToFit) {
_gameTopX = _gameTopY = _gameBottomX = _gameBottomY = 0;
_gameTopTexture.setScale(400.f / _gameWidth, 240.f / _gameHeight);
_gameBottomTexture.setScale(320.f / _gameWidth, 240.f / _gameHeight);
} else {
float ratio = static_cast<float>(_gameWidth) / _gameHeight;
if (ratio > 400.f / 240.f) {
float r = 400.f / _gameWidth;
_gameTopTexture.setScale(r, r);
_gameTopX = 0;
_gameTopY = (240.f / r - _gameHeight) / 2.f;
} else {
float r = 240.f / _gameHeight;
_gameTopTexture.setScale(r, r);
_gameTopY = 0;
_gameTopX = (400.f / r - _gameWidth) / 2.f;
}
if (ratio > 320.f / 240.f) {
float r = 320.f / _gameWidth;
_gameBottomTexture.setScale(r, r);
_gameBottomX = 0;
_gameBottomY = (240.f / r - _gameHeight) / 2.f;
} else {
float r = 240.f / _gameHeight;
_gameBottomTexture.setScale(r, r);
_gameBottomY = 0;
_gameBottomX = (320.f / r - _gameWidth) / 2.f;
}
}
_gameTopTexture.setPosition(_gameTopX, _gameTopY);
_gameBottomTexture.setPosition(_gameBottomX, _gameBottomY);
_gameTopTexture.setOffset(0, 0);
_gameBottomTexture.setOffset(0, 0);
if (_overlayInGUI) {
_cursorTexture.setScale(1.f, 1.f);
} else if (_screen == kScreenTop) {
_cursorTexture.setScale(_gameTopTexture.getScaleX(), _gameTopTexture.getScaleY());
} else {
_cursorTexture.setScale(_gameBottomTexture.getScaleX(), _gameBottomTexture.getScaleY());
}
}
Common::List<Graphics::PixelFormat> OSystem_3DS::getSupportedFormats() const {
Common::List<Graphics::PixelFormat> list;
// The following formats are supported natively by the GPU
list.push_back(Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)); // GPU_RGBA8
list.push_back(Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); // GPU_RGB565
list.push_back(Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0)); // GPU_RGBA5551
list.push_back(Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0)); // GPU_RGBA4
// The following format requires software conversion
list.push_back(Graphics::PixelFormat::createFormatCLUT8());
return list;
}
void OSystem_3DS::beginGFXTransaction() {
assert(_transactionState == kTransactionNone);
_transactionState = kTransactionActive;
_transactionDetails.formatChanged = false;
_oldGfxState = _gfxState;
}
OSystem::TransactionError OSystem_3DS::endGFXTransaction() {
int errors = OSystem::kTransactionSuccess;
assert(_transactionState != kTransactionNone);
if (_transactionState == kTransactionRollback) {
if (_gfxState.gfxModeID != _oldGfxState.gfxModeID) {
errors |= OSystem::kTransactionModeSwitchFailed;
_gfxState = _oldGfxState;
} else if ((_gfxState.gfxMode != _oldGfxState.gfxMode) |
(_gfxState.gfxMode != gfxModes[_gfxState.gfxModeID])) {
errors |= OSystem::kTransactionFormatNotSupported;
_gfxState = _oldGfxState;
}
_oldGfxState.setup = false;
}
if (_transactionDetails.formatChanged) {
if (!setGraphicsMode(_gfxState.gfxModeID)) {
if (_oldGfxState.setup) {
_transactionState = kTransactionRollback;
errors |= endGFXTransaction();
}
} else if (_gfxState.gfxMode != gfxModes[_gfxState.gfxModeID]) {
if (_oldGfxState.setup) {
_transactionState = kTransactionRollback;
errors |= endGFXTransaction();
}
} else {
initSize(_gameWidth, _gameHeight, &_pfGame);
clearOverlay();
_gfxState.setup = true;
_screenChangeId++;
}
}
_transactionState = kTransactionNone;
return (OSystem::TransactionError)errors;
}
float OSystem_3DS::getScaleRatio() const {
if (_overlayInGUI) {
return 1.0;
} else if (_screen == kScreenTop) {
return _gameTopTexture.getScaleX();
} else {
return _gameBottomTexture.getScaleX();
}
}
void OSystem_3DS::setPalette(const byte *colors, uint start, uint num) {
assert(start + num <= 256);
memcpy(_palette + 3 * start, colors, 3 * num);
Graphics::convertPaletteToMap(_paletteMap + start, colors, num, _modeCLUT8.surfaceFormat);
_gameTextureDirty = true;
}
void OSystem_3DS::grabPalette(byte *colors, uint start, uint num) const {
assert(start + num <= 256);
memcpy(colors, _palette + 3 * start, 3 * num);
}
// TODO: Move this into common code
// TODO: Convert two pixels at once using 32-bit loads and stores
static void copyRect555To5551(byte *dst, const byte *src, const uint dstPitch, const uint srcPitch,
const uint w, const uint h) {
// Faster, but larger, to provide optimized handling for each case.
const uint srcDelta = (srcPitch - w * sizeof(uint16));
const uint dstDelta = (dstPitch - w * sizeof(uint16));
for (uint y = 0; y < h; ++y) {
for (uint x = 0; x < w; ++x) {
uint16 col = *(const uint16 *)src;
col = (col << 1) | 1;
*(uint16 *)dst = col;
src += sizeof(uint16);
dst += sizeof(uint16);
}
src += srcDelta;
dst += dstDelta;
}
}
void OSystem_3DS::copyRectToScreen(const void *buf, int pitch, int x,
int y, int w, int h) {
if (_pfGame == _gameTopTexture.format) {
_gameTopTexture.copyRectToSurface(buf, pitch, x, y, w, h);
_gameTopTexture.markDirty();
return;
}
_gameScreen.copyRectToSurface(buf, pitch, x, y, w, h);
if (_pfGame == Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)) {
byte *dst = (byte *)_gameTopTexture.getBasePtr(x, y);
copyRect555To5551(dst, (const byte *)buf, _gameTopTexture.pitch, pitch, w, h);
} else if (_gfxState.gfxMode == &_modeCLUT8) {
byte *dst = (byte *)_gameTopTexture.getBasePtr(x, y);
Graphics::crossBlitMap(dst, (const byte *)buf, _gameTopTexture.pitch, pitch,
w, h, _gameTopTexture.format.bytesPerPixel, _paletteMap);
} else {
byte *dst = (byte *)_gameTopTexture.getBasePtr(x, y);
Graphics::crossBlit(dst, (const byte *)buf, _gameTopTexture.pitch, pitch,
w, h, _gameTopTexture.format, _pfGame);
}
_gameTopTexture.markDirty();
}
void OSystem_3DS::flushGameScreen() {
if (_pfGame == _gameTopTexture.format) {
return;
} else if (_pfGame == Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)) {
const byte *src = (const byte *)_gameScreen.getPixels();
byte *dst = (byte *)_gameTopTexture.getPixels();
copyRect555To5551(dst, src, _gameTopTexture.pitch, _gameScreen.pitch,
_gameScreen.w, _gameScreen.h);
} else if (_gfxState.gfxMode == &_modeCLUT8) {
const byte *src = (const byte *)_gameScreen.getPixels();
byte *dst = (byte *)_gameTopTexture.getPixels();
Graphics::crossBlitMap(dst, src, _gameTopTexture.pitch, _gameScreen.pitch,
_gameScreen.w, _gameScreen.h, _gameTopTexture.format.bytesPerPixel, _paletteMap);
} else {
const byte *src = (const byte *)_gameScreen.getPixels();
byte *dst = (byte *)_gameTopTexture.getPixels();
Graphics::crossBlit(dst, src, _gameTopTexture.pitch, _gameScreen.pitch,
_gameScreen.w, _gameScreen.h, _gameTopTexture.format, _pfGame);
}
_gameTopTexture.markDirty();
}
Graphics::Surface *OSystem_3DS::lockScreen() {
if (_pfGame == _gameTopTexture.format)
return &_gameTopTexture;
else
return &_gameScreen;
}
void OSystem_3DS::unlockScreen() {
_gameTextureDirty = true;
}
void OSystem_3DS::updateScreen() {
if (sleeping || exiting) {
return;
}
if (_gameTextureDirty) {
flushGameScreen();
_gameTextureDirty = false;
}
// updateFocus();
updateMagnify();
if (_osdMessage.getPixels() && _osdMessageEndTime <= getMillis(true)) {
_osdMessage.free();
}
C3D_FrameBegin(0);
_gameTopTexture.transfer();
if (_overlayVisible) {
_overlay.transfer();
}
if (_cursorVisible && _showCursor) {
_cursorTexture.transfer();
}
_osdMessage.transfer();
_activityIcon.transfer();
C3D_FrameEnd(0);
C3D_FrameBegin(0);
// Render top screen
C3D_RenderTargetClear(_renderTargetTop, C3D_CLEAR_ALL, 0x00000000, 0);
C3D_FrameDrawOn(_renderTargetTop);
if (_screen == kScreenTop || _screen == kScreenBoth) {
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _projectionLocation, &_projectionTop);
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _gameTopTexture.getMatrix());
_gameTopTexture.setFilteringMode(_magnifyMode != MODE_MAGON && _filteringEnabled);
_gameTopTexture.render();
if (_overlayVisible && _screen == kScreenTop) {
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _overlay.getMatrix());
_overlay.render();
}
if (_activityIcon.getPixels() && _screen == kScreenTop) {
_activityIcon.setPosition(400 - _activityIcon.actualWidth, 0);
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _activityIcon.getMatrix());
_activityIcon.render();
}
if (_osdMessage.getPixels() && _screen == kScreenTop) {
_osdMessage.setPosition((400 - _osdMessage.actualWidth) / 2, (240 - _osdMessage.actualHeight) / 2);
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _osdMessage.getMatrix());
_osdMessage.render();
}
if (_cursorVisible && _showCursor && _screen == kScreenTop) {
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _cursorTexture.getMatrix());
_cursorTexture.setFilteringMode(!_overlayInGUI && _filteringEnabled);
_cursorTexture.render();
}
}
// Render bottom screen
C3D_RenderTargetClear(_renderTargetBottom, C3D_CLEAR_ALL, 0x00000000, 0);
C3D_FrameDrawOn(_renderTargetBottom);
if (_screen == kScreenBottom || _screen == kScreenBoth) {
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _projectionLocation, &_projectionBottom);
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _gameBottomTexture.getMatrix());
_gameTopTexture.setFilteringMode(_filteringEnabled);
_gameTopTexture.render();
if (_overlayVisible) {
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _overlay.getMatrix());
_overlay.render();
}
if (_activityIcon.getPixels()) {
_activityIcon.setPosition(320 - _activityIcon.actualWidth, 0);
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _activityIcon.getMatrix());
_activityIcon.render();
}
if (_osdMessage.getPixels()) {
_osdMessage.setPosition((320 - _osdMessage.actualWidth) / 2, (240 - _osdMessage.actualHeight) / 2);
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _osdMessage.getMatrix());
_osdMessage.render();
}
if (_cursorVisible && _showCursor) {
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _cursorTexture.getMatrix());
_cursorTexture.setFilteringMode(!_overlayInGUI && _filteringEnabled);
_cursorTexture.render();
}
}
C3D_FrameEnd(0);
}
void OSystem_3DS::setShakePos(int shakeXOffset, int shakeYOffset) {
// TODO: implement this in overlay, top screen, and mouse too
_screenShakeXOffset = shakeXOffset;
_screenShakeYOffset = shakeYOffset;
int topX = _gameTopX + (_gameTopTexture.getScaleX() * shakeXOffset);
int topY = _gameTopY + (_gameTopTexture.getScaleY() * shakeYOffset);
_gameTopTexture.setPosition(topX, topY);
int bottomX = _gameBottomX + (_gameBottomTexture.getScaleX() * shakeXOffset);
int bottomY = _gameBottomY + (_gameBottomTexture.getScaleY() * shakeYOffset);
_gameBottomTexture.setPosition(bottomX, bottomY);
}
void OSystem_3DS::setFocusRectangle(const Common::Rect &rect) {
debug("setfocus: %d %d %d %d", rect.left, rect.top, rect.width(), rect.height());
_focusRect = rect;
_focusDirty = true;
_focusClearTime = 0;
}
void OSystem_3DS::clearFocusRectangle() {
_focusClearTime = getMillis();
}
void OSystem_3DS::updateFocus() {
if (_focusClearTime && getMillis() - _focusClearTime > 5000) {
_focusClearTime = 0;
_focusDirty = true;
_focusRect = Common::Rect(_gameWidth, _gameHeight);
}
if (_focusDirty) {
float duration = 1.f / 20.f; // Focus animation in frame duration
float w = 400.f;
float h = 240.f;
float ratio = _focusRect.width() / _focusRect.height();
if (ratio > w/h) {
_focusTargetScaleX = w / _focusRect.width();
float newHeight = (float)_focusRect.width() / w/h;
_focusTargetScaleY = h / newHeight;
_focusTargetPosX = _focusTargetScaleX * _focusRect.left;
_focusTargetPosY = _focusTargetScaleY * ((float)_focusRect.top - (newHeight - _focusRect.height())/2.f);
} else {
_focusTargetScaleY = h / _focusRect.height();
float newWidth = (float)_focusRect.height() * w/h;
_focusTargetScaleX = w / newWidth;
_focusTargetPosY = _focusTargetScaleY * _focusRect.top;
_focusTargetPosX = _focusTargetScaleX * ((float)_focusRect.left - (newWidth - _focusRect.width())/2.f);
}
if (_focusTargetPosX < 0 && _focusTargetScaleY != 240.f / _gameHeight) {
_focusTargetPosX = 0;
}
if (_focusTargetPosY < 0 && _focusTargetScaleX != 400.f / _gameWidth) {
_focusTargetPosY = 0;
}
_focusStepPosX = duration * (_focusTargetPosX - _focusPosX);
_focusStepPosY = duration * (_focusTargetPosY - _focusPosY);
_focusStepScaleX = duration * (_focusTargetScaleX - _focusScaleX);
_focusStepScaleY = duration * (_focusTargetScaleY - _focusScaleY);
}
if (_focusDirty || _focusPosX != _focusTargetPosX || _focusPosY != _focusTargetPosY ||
_focusScaleX != _focusTargetScaleX || _focusScaleY != _focusTargetScaleY) {
_focusDirty = false;
if ((_focusStepPosX > 0 && _focusPosX > _focusTargetPosX) || (_focusStepPosX < 0 && _focusPosX < _focusTargetPosX)) {
_focusPosX = _focusTargetPosX;
} else if (_focusPosX != _focusTargetPosX) {
_focusPosX += _focusStepPosX;
}
if ((_focusStepPosY > 0 && _focusPosY > _focusTargetPosY) || (_focusStepPosY < 0 && _focusPosY < _focusTargetPosY)) {
_focusPosY = _focusTargetPosY;
} else if (_focusPosY != _focusTargetPosY) {
_focusPosY += _focusStepPosY;
}
if ((_focusStepScaleX > 0 && _focusScaleX > _focusTargetScaleX) || (_focusStepScaleX < 0 && _focusScaleX < _focusTargetScaleX)) {
_focusScaleX = _focusTargetScaleX;
} else if (_focusScaleX != _focusTargetScaleX) {
_focusScaleX += _focusStepScaleX;
}
if ((_focusStepScaleY > 0 && _focusScaleY > _focusTargetScaleY) || (_focusStepScaleY < 0 && _focusScaleY < _focusTargetScaleY)) {
_focusScaleY = _focusTargetScaleY;
} else if (_focusScaleY != _focusTargetScaleY) {
_focusScaleY += _focusStepScaleY;
}
Mtx_Identity(&_focusMatrix);
Mtx_Translate(&_focusMatrix, -_focusPosX, -_focusPosY, 0, true);
Mtx_Scale(&_focusMatrix, _focusScaleX, _focusScaleY, 1.f);
}
}
void OSystem_3DS::updateMagnify() {
if (_magnifyMode == MODE_MAGON && _screen != kScreenBoth) {
// Only allow to magnify when both screens are enabled
_magnifyMode = MODE_MAGOFF;
}
if (_magnifyMode == MODE_MAGON) {
if (!_overlayVisible) {
_magX = (_cursorScreenX < _magCenterX) ?
0 : ((_cursorScreenX < (_gameWidth - _magCenterX)) ?
_cursorScreenX - _magCenterX : _gameWidth - _magWidth);
_magY = (_cursorScreenY < _magCenterY) ?
0 : ((_cursorScreenY < _gameHeight - _magCenterY) ?
_cursorScreenY - _magCenterY : _gameHeight - _magHeight);
}
_gameTopTexture.setScale(1.f,1.f);
_gameTopTexture.setPosition(0,0);
_gameTopTexture.setOffset(_magX, _magY);
}
}
void OSystem_3DS::showOverlay(bool inGUI) {
_overlayInGUI = inGUI;
_overlayVisible = true;
updateSize();
}
void OSystem_3DS::hideOverlay() {
_overlayVisible = false;
_overlayInGUI = false;
updateSize();
}
Graphics::PixelFormat OSystem_3DS::getOverlayFormat() const {
return _overlay.format;
}
void OSystem_3DS::clearOverlay() {
_overlay.clear();
}
void OSystem_3DS::grabOverlay(Graphics::Surface &surface) {
assert(surface.w >= getOverlayWidth());
assert(surface.h >= getOverlayHeight());
assert(surface.format.bytesPerPixel == _overlay.format.bytesPerPixel);
byte *src = (byte *)_overlay.getPixels();
byte *dst = (byte *)surface.getPixels();
Graphics::copyBlit(dst, src, surface.pitch, _overlay.pitch,
getOverlayWidth(), getOverlayHeight(), _overlay.format.bytesPerPixel);
}
void OSystem_3DS::copyRectToOverlay(const void *buf, int pitch, int x,
int y, int w, int h) {
_overlay.copyRectToSurface(buf, pitch, x, y, w, h);
_overlay.markDirty();
}
void OSystem_3DS::displayMessageOnOSD(const Common::U32String &msg) {
// The font we are going to use:
const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont);
if (!font) {
warning("No available font to render OSD messages");
return;
}
// Split the message into separate lines.
Common::Array<Common::U32String> lines;
Common::U32String::const_iterator strLineItrBegin = msg.begin();
for (Common::U32String::const_iterator itr = msg.begin(); itr != msg.end(); itr++) {
if (*itr == '\n') {
lines.push_back(Common::U32String(strLineItrBegin, itr));
strLineItrBegin = itr + 1;
}
}
if (strLineItrBegin != msg.end())
lines.push_back(Common::U32String(strLineItrBegin, msg.end()));
// Determine a rect which would contain the message string (clipped to the
// screen dimensions).
const int vOffset = 6;
const int lineSpacing = 1;
const int lineHeight = font->getFontHeight() + 2 * lineSpacing;
int width = 0;
int height = lineHeight * lines.size() + 2 * vOffset;
uint i;
for (i = 0; i < lines.size(); i++) {
width = MAX(width, font->getStringWidth(lines[i]) + 14);
}
// Clip the rect
if (width > getOverlayWidth()) {
width = getOverlayWidth();
}
if (height > getOverlayHeight()) {
height = getOverlayHeight();
}
_osdMessage.create(width, height, &DEFAULT_MODE);
_osdMessage.fillRect(Common::Rect(width, height), _pfDefaultTexture.ARGBToColor(200, 0, 0, 0));
// Render the message, centered, and in white
for (i = 0; i < lines.size(); i++) {
font->drawString(&_osdMessage, lines[i],
0, 0 + i * lineHeight + vOffset + lineSpacing, width,
_pfDefaultTexture.RGBToColor(255, 255, 255),
Graphics::kTextAlignCenter, 0, true);
}
_osdMessageEndTime = getMillis(true) + kOSDMessageDuration;
}
void OSystem_3DS::displayActivityIconOnOSD(const Graphics::Surface *icon) {
if (!icon) {
_activityIcon.free();
} else {
if (!_activityIcon.getPixels() || icon->w != _activityIcon.w || icon->h != _activityIcon.h) {
_activityIcon.create(icon->w, icon->h, &DEFAULT_MODE);
}
if (icon->format == _activityIcon.format) {
_activityIcon.copyRectToSurface(*icon, 0, 0, Common::Rect(icon->w, icon->h));
} else {
Graphics::Surface *converted = icon->convertTo(_activityIcon.format);
_activityIcon.copyRectToSurface(*converted, 0, 0, Common::Rect(converted->w, converted->h));
converted->free();
delete converted;
}
_activityIcon.markDirty();
}
}
int16 OSystem_3DS::getOverlayHeight() const {
return 240;
}
int16 OSystem_3DS::getOverlayWidth() const {
return _screen == kScreenTop ? 400 : 320;
}
bool OSystem_3DS::showMouse(bool visible) {
_cursorVisible = visible;
flushCursor();
return !visible;
}
void OSystem_3DS::warpMouse(int x, int y) {
if (!_overlayVisible) {
_cursorScreenX = x;
_cursorScreenY = y;
} else {
_cursorOverlayX = x;
_cursorOverlayY = y;
}
// TODO: adjust for _cursorScalable ?
x -= _cursorHotspotX;
y -= _cursorHotspotY;
int offsetx = 0;
int offsety = 0;
if (!_overlayVisible) {
offsetx = _screen == kScreenTop ? _gameTopTexture.getPosX() : _gameBottomTexture.getPosX();
offsety = _screen == kScreenTop ? _gameTopTexture.getPosY() : _gameBottomTexture.getPosY();
}
_cursorTexture.setPosition(x + offsetx, y + offsety);
}
void OSystem_3DS::setCursorDelta(float deltaX, float deltaY) {
_cursorDeltaX = deltaX;
_cursorDeltaY = deltaY;
}
void OSystem_3DS::setMouseCursor(const void *buf, uint w, uint h,
int hotspotX, int hotspotY,
uint32 keycolor, bool dontScale,
const Graphics::PixelFormat *format, const byte *mask) {
_cursorScalable = !dontScale;
_cursorHotspotX = hotspotX;
_cursorHotspotY = hotspotY;
_cursorKeyColor = keycolor;
_pfCursor = !format ? Graphics::PixelFormat::createFormatCLUT8() : *format;
if (mask)
warning("OSystem_3DS::setMouseCursor: Masks are not supported");
if (w != (uint)_cursor.w || h != (uint)_cursor.h || _cursor.format != _pfCursor) {
_cursor.create(w, h, _pfCursor);
_cursorTexture.create(w, h, &DEFAULT_MODE);
}
if ( w != 0 && h != 0 ) {
_cursor.copyRectToSurface(buf, w * _pfCursor.bytesPerPixel, 0, 0, w, h);
}
flushCursor();
if (!_overlayVisible) {
warpMouse(_cursorScreenX, _cursorScreenY);
} else {
warpMouse(_cursorOverlayX, _cursorOverlayY);
}
}
void OSystem_3DS::setCursorPalette(const byte *colors, uint start, uint num) {
assert(start + num <= 256);
memcpy(_cursorPalette + 3 * start, colors, 3 * num);
_cursorPaletteEnabled = true;
flushCursor();
}
namespace {
template<typename SrcColor>
void applyKeyColor(Graphics::Surface *src, Graphics::Surface *dst, const SrcColor keyColor) {
assert(dst->format.bytesPerPixel == 4);
assert((dst->w >= src->w) && (dst->h >= src->h));
for (uint y = 0; y < (uint)src->h; ++y) {
SrcColor *srcPtr = (SrcColor *)src->getBasePtr(0, y);
uint32 *dstPtr = (uint32 *)dst->getBasePtr(0, y);
for (uint x = 0; x < (uint)src->w; ++x) {
const SrcColor color = *srcPtr++;
if (color == keyColor) {
*dstPtr = 0;
}
dstPtr++;
}
}
}
} // End of anonymous namespace
void OSystem_3DS::flushCursor() {
if (_cursor.getPixels()) {
Graphics::Surface *converted = _cursor.convertTo(_cursorTexture.format, _cursorPaletteEnabled ? _cursorPalette : _palette);
_cursorTexture.copyRectToSurface(*converted, 0, 0, Common::Rect(converted->w, converted->h));
_cursorTexture.markDirty();
converted->free();
delete converted;
if (_pfCursor.bytesPerPixel == 1) {
applyKeyColor<byte>(&_cursor, &_cursorTexture, _cursorKeyColor);
} else if (_pfCursor.bytesPerPixel == 2) {
applyKeyColor<uint16>(&_cursor, &_cursorTexture, _cursorKeyColor);
} else if (_pfCursor.bytesPerPixel == 4) {
applyKeyColor<uint32>(&_cursor, &_cursorTexture, _cursorKeyColor);
}
}
}
} // namespace N3DS

View File

@@ -0,0 +1,275 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
#define FORBIDDEN_SYMBOL_EXCEPTION_unistd_h
#include <3ds.h>
#include "osystem.h"
#include "backends/mutex/3ds/3ds-mutex.h"
#include "backends/saves/default/default-saves.h"
#include "backends/timer/default/default-timer.h"
#include "backends/events/default/default-events.h"
#include "audio/mixer_intern.h"
#include "common/scummsys.h"
#include "common/config-manager.h"
#include "common/str.h"
#include "backends/fs/posix-drives/posix-drives-fs-factory.h"
#include "backends/fs/posix-drives/posix-drives-fs.h"
#include <unistd.h>
#include <time.h>
namespace N3DS {
OSystem_3DS::OSystem_3DS():
_focusDirty(true),
_focusRect(Common::Rect(1, 1)),
_focusPosX(0),
_focusPosY(0),
_focusTargetPosX(0),
_focusTargetPosY(0),
_focusStepPosX(0),
_focusStepPosY(0),
_focusScaleX(1.f),
_focusScaleY(1.f),
_focusTargetScaleX(1.f),
_focusTargetScaleY(1.f),
_focusStepScaleX(0.f),
_focusStepScaleY(0.f),
_focusClearTime(0),
_cursorPaletteEnabled(false),
_cursorVisible(false),
_cursorScalable(false),
_cursorScreenX(0),
_cursorScreenY(0),
_cursorOverlayX(0),
_cursorOverlayY(0),
_cursorHotspotX(0),
_cursorHotspotY(0),
_gameTopX(0),
_gameTopY(0),
_gameBottomX(0),
_gameBottomY(0),
_gameWidth(320),
_gameHeight(240),
_magX(0),
_magY(0),
_magWidth(400),
_magHeight(240),
_gameTextureDirty(false),
_filteringEnabled(true),
_overlayVisible(false),
_overlayInGUI(false),
_screenChangeId(0),
_magnifyMode(MODE_MAGOFF),
exiting(false),
sleeping(false),
_logger(0),
_showCursor(true),
_snapToBorder(true),
_stretchToFit(false),
_screen(kScreenBoth)
{
chdir("sdmc:/");
DrivesPOSIXFilesystemFactory *fsFactory = new DrivesPOSIXFilesystemFactory();
fsFactory->addDrive("sdmc:");
fsFactory->addDrive("romfs:");
//
// Disable newlib's buffered IO, and use ScummVM's own buffering stream wrappers.
//
// The newlib version in use in devkitPro has performance issues
// when seeking with a relative offset. See:
// https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=commit;h=59362c80e3a02c011fd0ef3d7f07a20098d2a9d5
//
// devKitPro has a patch to newlib that can cause data corruption when
// seeking back in files and then reading. See:
// https://github.com/devkitPro/newlib/issues/16
//
fsFactory->configureBuffering(DrivePOSIXFilesystemNode::kBufferingModeScummVM, 2048);
_fsFactory = fsFactory;
Posix::assureDirectoryExists("/3ds/scummvm/saves/");
}
OSystem_3DS::~OSystem_3DS() {
exiting = true;
destroyEvents();
destroyAudio();
destroy3DSGraphics();
delete _logger;
_logger = 0;
delete _timerManager;
_timerManager = 0;
}
void OSystem_3DS::quit() {
printf("OSystem_3DS::quit()\n");
}
void OSystem_3DS::initBackend() {
if (!_logger)
_logger = new Backends::Log::Log(this);
if (_logger) {
Common::WriteStream *logFile = createLogFile();
if (logFile)
_logger->open(logFile);
}
updateBacklight();
updateConfig();
ConfMan.registerDefault("fullscreen", true);
ConfMan.registerDefault("aspect_ratio", true);
ConfMan.registerDefault("filtering", true);
if (!ConfMan.hasKey("vkeybd_pack_name")) {
ConfMan.set("vkeybd_pack_name", "vkeybd_small");
}
_timerManager = new DefaultTimerManager();
_savefileManager = new DefaultSaveFileManager("sdmc:/3ds/scummvm/saves/");
init3DSGraphics();
initAudio();
EventsBaseBackend::initBackend();
initEvents();
}
void OSystem_3DS::updateBacklight() {
// Turn off the backlight of any screen not used
if (R_SUCCEEDED(gspLcdInit())) {
if (_screen == kScreenTop) {
GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_TOP);
GSPLCD_PowerOffBacklight(GSPLCD_SCREEN_BOTTOM);
} else if (_screen == kScreenBottom) {
GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_BOTTOM);
GSPLCD_PowerOffBacklight(GSPLCD_SCREEN_TOP);
} else {
GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_BOTH);
}
gspLcdExit();
}
}
void OSystem_3DS::updateConfig() {
if (_gameScreen.getPixels()) {
updateSize();
(!_overlayVisible) ? warpMouse(_cursorScreenX, _cursorScreenY) :
warpMouse(_cursorOverlayX, _cursorOverlayY);
}
}
Common::Path OSystem_3DS::getDefaultConfigFileName() {
return "sdmc:/3ds/scummvm/scummvm.ini";
}
Common::Path OSystem_3DS::getDefaultLogFileName() {
return "sdmc:/3ds/scummvm/scummvm.log";
}
void OSystem_3DS::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) {
s.add("RomFS", new Common::FSDirectory(DATA_PATH"/"), priority);
}
uint32 OSystem_3DS::getMillis(bool skipRecord) {
return svcGetSystemTick() / TICKS_PER_MSEC;
}
void OSystem_3DS::delayMillis(uint msecs) {
svcSleepThread(msecs * 1000000);
}
void OSystem_3DS::getTimeAndDate(TimeDate& td, bool skipRecord) const {
time_t curTime = time(0);
struct tm t = *localtime(&curTime);
td.tm_sec = t.tm_sec;
td.tm_min = t.tm_min;
td.tm_hour = t.tm_hour;
td.tm_mday = t.tm_mday;
td.tm_mon = t.tm_mon;
td.tm_year = t.tm_year;
td.tm_wday = t.tm_wday;
}
Common::MutexInternal *OSystem_3DS::createMutex() {
return create3DSMutexInternal();
}
Common::String OSystem_3DS::getSystemLanguage() const {
u8 langcode;
CFGU_GetSystemLanguage(&langcode);
switch (langcode) {
case CFG_LANGUAGE_JP: return "ja_JP";
case CFG_LANGUAGE_EN: return "en_US";
case CFG_LANGUAGE_FR: return "fr_FR";
case CFG_LANGUAGE_DE: return "de_DE";
case CFG_LANGUAGE_IT: return "it_IT";
case CFG_LANGUAGE_ES: return "es_ES";
case CFG_LANGUAGE_ZH: return "zh_CN";
case CFG_LANGUAGE_KO: return "ko_KR";
case CFG_LANGUAGE_NL: return "nl_NL";
case CFG_LANGUAGE_PT: return "pt_BR";
case CFG_LANGUAGE_RU: return "ru_RU";
case CFG_LANGUAGE_TW: return "zh_HK";
default: return "en_US";
}
}
void OSystem_3DS::fatalError() {
printf("FatalError!\n");
}
void OSystem_3DS::logMessage(LogMessageType::Type type, const char *message) {
printf("%s", message);
// Then log into file (via the logger)
if (_logger)
_logger->print(message);
}
Common::WriteStream *OSystem_3DS::createLogFile() {
// Start out by resetting _logFilePath, so that in case
// of a failure, we know that no log file is open.
_logFilePath.clear();
Common::Path logFile;
if (ConfMan.hasKey("logfile"))
logFile = ConfMan.getPath("logfile");
else
logFile = getDefaultLogFileName();
if (logFile.empty())
return nullptr;
Common::FSNode file(logFile);
Common::WriteStream *stream = file.createWriteStream(false);
if (stream)
_logFilePath = logFile;
return stream;
}
} // namespace N3DS

View File

@@ -0,0 +1,316 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PLATFORM_3DS_H
#define PLATFORM_3DS_H
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
#include "backends/base-backend.h"
#include "graphics/paletteman.h"
#include "base/main.h"
#include "audio/mixer_intern.h"
#include "backends/graphics/graphics.h"
#include "backends/log/log.h"
#include "backends/platform/3ds/sprite.h"
#include "common/rect.h"
#include "common/queue.h"
#include "common/ustr.h"
#include "engines/engine.h"
#define TICKS_PER_MSEC 268123
namespace N3DS {
enum MagnifyMode {
MODE_MAGON,
MODE_MAGOFF,
};
enum InputMode {
MODE_HOVER,
MODE_DRAG,
};
enum GraphicsModeID {
RGBA8,
RGB565,
RGB5A1,
RGBA4,
CLUT8
};
enum Screen {
kScreenTop = 0x10000002,
kScreenBottom,
kScreenBoth,
};
enum TransactionState {
kTransactionNone = 0,
kTransactionActive = 1,
kTransactionRollback = 2
};
struct TransactionDetails {
bool formatChanged, modeChanged;
TransactionDetails() {
formatChanged = false;
modeChanged = false;
}
};
typedef struct GfxMode3DS {
Graphics::PixelFormat surfaceFormat;
GPU_TEXCOLOR textureFormat;
uint32 textureTransferFlags;
} GfxMode3DS;
struct GfxState {
bool setup;
GraphicsModeID gfxModeID;
const GfxMode3DS *gfxMode;
GfxState() {
setup = false;
gfxModeID = CLUT8;
}
};
class OSystem_3DS : public EventsBaseBackend, public PaletteManager, public Common::EventObserver {
public:
OSystem_3DS();
virtual ~OSystem_3DS();
volatile bool exiting;
volatile bool sleeping;
virtual void initBackend();
virtual bool hasFeature(OSystem::Feature f);
virtual void setFeatureState(OSystem::Feature f, bool enable);
virtual bool getFeatureState(OSystem::Feature f);
bool pollEvent(Common::Event &event) override;
bool notifyEvent(const Common::Event &event) override;
Common::HardwareInputSet *getHardwareInputSet() override;
Common::KeymapArray getGlobalKeymaps() override;
Common::KeymapperDefaultBindings *getKeymapperDefaultBindings() override;
void registerDefaultSettings(const Common::String &target) const override;
GUI::OptionsContainerWidget *buildBackendOptionsWidget(GUI::GuiObject *boss, const Common::String &name, const Common::String &target) const override;
void applyBackendSettings() override;
virtual uint32 getMillis(bool skipRecord = false);
virtual void delayMillis(uint msecs);
virtual void getTimeAndDate(TimeDate &td, bool skipRecord = false) const;
virtual Common::MutexInternal *createMutex();
virtual void logMessage(LogMessageType::Type type, const char *message);
virtual Audio::Mixer *getMixer();
virtual PaletteManager *getPaletteManager() { return this; }
virtual Common::String getSystemLanguage() const;
virtual void fatalError();
virtual void quit();
virtual Common::Path getDefaultConfigFileName();
void addSysArchivesToSearchSet(Common::SearchSet &s, int priority) override;
// Graphics
inline Graphics::PixelFormat getScreenFormat() const { return _pfGame; }
virtual Common::List<Graphics::PixelFormat> getSupportedFormats() const;
void initSize(uint width, uint height,
const Graphics::PixelFormat *format = NULL);
virtual int getScreenChangeID() const { return _screenChangeId; };
GraphicsModeID chooseMode(Graphics::PixelFormat *format);
bool setGraphicsMode(GraphicsModeID modeID);
void beginGFXTransaction();
OSystem::TransactionError endGFXTransaction();
int16 getHeight(){ return _gameHeight; }
int16 getWidth(){ return _gameWidth; }
float getScaleRatio() const;
void setPalette(const byte *colors, uint start, uint num);
void grabPalette(byte *colors, uint start, uint num) const;
void copyRectToScreen(const void *buf, int pitch, int x, int y, int w,
int h);
Graphics::Surface *lockScreen();
void unlockScreen();
void updateScreen();
void setShakePos(int shakeXOffset, int shakeYOffset);
void setFocusRectangle(const Common::Rect &rect);
void clearFocusRectangle();
void showOverlay(bool inGUI);
void hideOverlay();
bool isOverlayVisible() const { return _overlayVisible; }
Graphics::PixelFormat getOverlayFormat() const;
void clearOverlay();
void grabOverlay(Graphics::Surface &surface);
void copyRectToOverlay(const void *buf, int pitch, int x, int y, int w,
int h);
virtual int16 getOverlayHeight() const;
virtual int16 getOverlayWidth() const;
void displayMessageOnOSD(const Common::U32String &msg) override;
void displayActivityIconOnOSD(const Graphics::Surface *icon) override;
bool showMouse(bool visible);
void warpMouse(int x, int y);
void setMouseCursor(const void *buf, uint w, uint h, int hotspotX,
int hotspotY, uint32 keycolor, bool dontScale = false,
const Graphics::PixelFormat *format = NULL, const byte *mask = NULL);
void setCursorPalette(const byte *colors, uint start, uint num);
// Transform point from touchscreen coords into gamescreen coords
void transformPoint(touchPosition &point);
// Clip point to gamescreen coords
void clipPoint(touchPosition &point);
void setCursorDelta(float deltaX, float deltaY);
void updateFocus();
void updateMagnify();
void updateBacklight();
void updateConfig();
void updateSize();
private:
void init3DSGraphics();
void destroy3DSGraphics();
void initAudio();
void destroyAudio();
void initEvents();
void destroyEvents();
void flushGameScreen();
void flushCursor();
virtual Common::Path getDefaultLogFileName();
virtual Common::WriteStream *createLogFile();
protected:
Audio::MixerImpl *_mixer;
Backends::Log::Log *_logger;
private:
u16 _gameWidth, _gameHeight;
u16 _gameTopX, _gameTopY;
u16 _gameBottomX, _gameBottomY;
// Audio
Thread audioThread;
// Graphics
GraphicsModeID _graphicsModeID;
TransactionState _transactionState;
TransactionDetails _transactionDetails;
GfxState _gfxState, _oldGfxState;
Graphics::PixelFormat _pfDefaultTexture;
Graphics::PixelFormat _pfGame, _oldPfGame;
Graphics::PixelFormat _pfCursor;
byte _palette[3 * 256];
byte _cursorPalette[3 * 256];
uint32 _paletteMap[256];
Graphics::Surface _gameScreen;
bool _gameTextureDirty;
Sprite _gameTopTexture;
Sprite _gameBottomTexture;
Sprite _overlay;
Sprite _activityIcon;
Sprite _osdMessage;
bool _filteringEnabled;
enum {
kOSDMessageDuration = 800
};
uint32 _osdMessageEndTime;
int _screenShakeXOffset;
int _screenShakeYOffset;
bool _overlayVisible;
bool _overlayInGUI;
int _screenChangeId;
DVLB_s *_dvlb;
shaderProgram_s _program;
int _projectionLocation;
int _modelviewLocation;
C3D_Mtx _projectionTop;
C3D_Mtx _projectionBottom;
C3D_RenderTarget* _renderTargetTop;
C3D_RenderTarget* _renderTargetBottom;
// Focus
Common::Rect _focusRect;
bool _focusDirty;
C3D_Mtx _focusMatrix;
int _focusPosX, _focusPosY;
int _focusTargetPosX, _focusTargetPosY;
float _focusStepPosX, _focusStepPosY;
float _focusScaleX, _focusScaleY;
float _focusTargetScaleX, _focusTargetScaleY;
float _focusStepScaleX, _focusStepScaleY;
uint32 _focusClearTime;
// Events
Thread _eventThread;
Thread _timerThread;
Common::Queue<Common::Event> _eventQueue;
// Cursor
Graphics::Surface _cursor;
Sprite _cursorTexture;
bool _cursorPaletteEnabled;
bool _cursorVisible;
bool _cursorScalable;
float _cursorScreenX, _cursorScreenY;
float _cursorOverlayX, _cursorOverlayY;
float _cursorDeltaX, _cursorDeltaY;
int _cursorHotspotX, _cursorHotspotY;
uint32 _cursorKeyColor;
// Magnify
MagnifyMode _magnifyMode;
u16 _magX, _magY;
u16 _magWidth, _magHeight;
u16 _magCenterX, _magCenterY;
Common::Path _logFilePath;
public:
// Pause
PauseToken _sleepPauseToken;
bool _showCursor;
bool _snapToBorder;
bool _stretchToFit;
Screen _screen;
};
} // namespace N3DS
#endif

View File

@@ -0,0 +1,56 @@
;* 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 3 of the License, or
;* 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, see <http://www.gnu.org/licenses/>.
; Uniforms
.fvec projection[4], modelView[4]
; Constants
.constf myconst(0.0, 1.0, -1.0, 0.1)
.alias zeros myconst.xxxx ; Vector full of zeros
.alias ones myconst.yyyy ; Vector full of ones
; Outputs
.out outpos position
.out outtex texcoord0
; Inputs (defined as aliases for convenience)
.alias inpos v0
.alias intex v1
.proc main
; Force the w component of inpos to be 1.0
mov r0.xyz, inpos
mov r0.w, ones
; r1 = modelView * inpos
dp4 r1.x, modelView[0], r0
dp4 r1.y, modelView[1], r0
dp4 r1.z, modelView[2], r0
dp4 r1.w, modelView[3], r0
; outpos = projection * r1
dp4 outpos.x, projection[0], r1
dp4 outpos.y, projection[1], r1
dp4 outpos.z, projection[2], r1
dp4 outpos.w, projection[3], r1
mov outtex, intex
end
.end

View File

@@ -0,0 +1,175 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/platform/3ds/osystem.h"
#include "backends/platform/3ds/sprite.h"
#include "common/algorithm.h"
#include "common/util.h"
namespace N3DS {
Sprite::Sprite()
: textureTransferFlags(0)
, dirtyPixels(true)
, dirtyMatrix(true)
, actualWidth(0)
, actualHeight(0)
, posX(0)
, posY(0)
, offsetX(0)
, offsetY(0)
, scaleX(1.f)
, scaleY(1.f)
{
Mtx_Identity(&modelview);
vertices = (vertex *)linearAlloc(sizeof(vertex) * 4);
}
Sprite::~Sprite() {
free();
linearFree(vertices);
}
void Sprite::create(uint16 width, uint16 height, const GfxMode3DS *mode, bool vram) {
int16 wPow = MAX<uint16>(Common::nextHigher2(width), 64u);
int16 hPow = MAX<uint16>(Common::nextHigher2(height), 64u);
bool pwrW_hChanged = (wPow != w) || (hPow != h);
bool sfcBppChanged = (mode->surfaceFormat.bytesPerPixel != format.bytesPerPixel);
bool texFmtChanged = (mode->textureFormat != texture.fmt);
bool srfDataReinitNeeded = (pwrW_hChanged || sfcBppChanged || !pixels);
bool textureReinitNeeded = (pwrW_hChanged || texFmtChanged || !texture.data);
actualWidth = width;
actualHeight = height;
format = mode->surfaceFormat;
textureTransferFlags = mode->textureTransferFlags;
w = wPow;
h = hPow;
pitch = w * format.bytesPerPixel;
dirtyPixels = true;
if (width && height) {
// Don't needlessly reinitialize surface pixels.
if (srfDataReinitNeeded) {
linearFree(pixels);
pixels = linearAlloc(h * pitch);
}
// Don't needlessly reinitialize C3D_Tex data.
if (textureReinitNeeded) {
C3D_TexDelete(&texture);
if (vram) {
if (!C3D_TexInitVRAM(&texture, w, h, mode->textureFormat))
C3D_TexInit(&texture, w, h, mode->textureFormat);
} else
C3D_TexInit(&texture, w, h, mode->textureFormat);
}
assert(pixels && texture.data);
clear();
}
float x = 0.f, y = 0.f;
float u = (float)width/w;
float v = (float)height/h;
vertex tmp[4] = {
{{x, y, 0.5f}, {0, 0}},
{{x+width, y, 0.5f}, {u, 0}},
{{x, y+height, 0.5f}, {0, v}},
{{x+width, y+height, 0.5f}, {u, v}},
};
memcpy(vertices, tmp, sizeof(vertex) * 4);
}
void Sprite::free() {
linearFree(pixels);
C3D_TexDelete(&texture);
pixels = 0;
w = h = pitch = 0;
actualWidth = actualHeight = 0;
format = Graphics::PixelFormat();
}
void Sprite::convertToInPlace(const Graphics::PixelFormat &dstFormat, const byte *palette) {
//
}
void Sprite::transfer() {
if (pixels && dirtyPixels) {
dirtyPixels = false;
GSPGPU_FlushDataCache(pixels, w * h * format.bytesPerPixel);
C3D_SyncDisplayTransfer((u32*)pixels, GX_BUFFER_DIM(w, h), (u32*)texture.data, GX_BUFFER_DIM(w, h), textureTransferFlags);
}
}
void Sprite::render() {
C3D_TexBind(0, &texture);
C3D_BufInfo *bufInfo = C3D_GetBufInfo();
BufInfo_Init(bufInfo);
BufInfo_Add(bufInfo, vertices, sizeof(vertex), 2, 0x10);
C3D_DrawArrays(GPU_TRIANGLE_STRIP, 0, 4);
}
void Sprite::clear(uint32 color) {
dirtyPixels = true;
memset(pixels, color, w * h * format.bytesPerPixel);
}
void Sprite::setScale (float x, float y) {
if (x != scaleX || y != scaleY) {
scaleX = x;
scaleY = y;
dirtyMatrix = true;
}
}
void Sprite::setPosition(int x, int y) {
if (x != posX || y != posY) {
posX = x;
posY = y;
dirtyMatrix = true;
}
}
void Sprite::setOffset(uint16 x, uint16 y) {
offsetX = x;
offsetY = y;
dirtyMatrix = true;
}
C3D_Mtx* Sprite::getMatrix() {
if (dirtyMatrix) {
dirtyMatrix = false;
Mtx_Identity(&modelview);
Mtx_Scale(&modelview, scaleX, scaleY, 1.f);
Mtx_Translate(&modelview, posX - offsetX, posY - offsetY, 0, true);
}
return &modelview;
}
void Sprite::setFilteringMode(bool enableLinearFiltering) {
GPU_TEXTURE_FILTER_PARAM filteringMode = enableLinearFiltering ? GPU_LINEAR : GPU_NEAREST;
C3D_TexSetFilter(&texture, filteringMode, filteringMode);
}
} // namespace N3DS

View File

@@ -0,0 +1,83 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRAPHICS_SPRITE_3DS_H
#define GRAPHICS_SPRITE_3DS_H
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
#include "graphics/surface.h"
#include <3ds.h>
#include <citro3d.h>
namespace N3DS {
typedef struct {
float position[3];
float texcoord[2];
} vertex;
struct GfxMode3DS;
class Sprite : public Graphics::Surface {
public:
Sprite();
~Sprite();
void create(uint16 width, uint16 height, const GfxMode3DS *mode, bool vram = false);
void free();
void convertToInPlace(const Graphics::PixelFormat &dstFormat, const byte *palette = 0);
void transfer();
void render();
void clear(uint32 color = 0);
void markDirty(){ dirtyPixels = true; }
void setPosition(int x, int y);
void setOffset(uint16 x, uint16 y);
void setScale(float x, float y);
float getScaleX() const { return scaleX; }
float getScaleY() const { return scaleY; }
int getPosX() const { return posX; }
int getPosY() const { return posY; }
C3D_Mtx* getMatrix();
void setFilteringMode(bool enableLinearFiltering);
uint16 actualWidth;
uint16 actualHeight;
private:
uint32 textureTransferFlags;
bool dirtyPixels;
bool dirtyMatrix;
C3D_Mtx modelview;
C3D_Tex texture;
vertex* vertices;
int posX;
int posY;
uint16 offsetX;
uint16 offsetY;
float scaleX;
float scaleY;
};
} // namespace N3DS
#endif