[gnome-games/wip/exalm/3ds] tmp




commit dedeeff2833a93f8e99e575391dcae3cbaef9895
Author: Alexander Mikhaylenko <alexm gnome org>
Date:   Tue Sep 15 20:35:05 2020 +0500

    tmp

 data/options/citra.options                         |   2 +
 data/options/meson.build                           |   1 +
 data/org.gnome.Games.desktop.in.in                 |   2 +-
 flatpak/libretro-cores/citra.libretro              |  11 ++
 flatpak/libretro-cores/libretro-citra.json         |  32 ++++++
 meson_options.txt                                  |   1 +
 plugins/meson.build                                |   1 +
 .../icons/screen-layout-left-right-symbolic.svg    |  70 ++++++++++++
 .../icons/screen-layout-quick-switch-symbolic.svg  |  66 +++++++++++
 .../icons/screen-layout-right-left-symbolic.svg    |  71 ++++++++++++
 .../icons/screen-layout-top-bottom-symbolic.svg    |  71 ++++++++++++
 .../data/icons/view-bottom-screen-symbolic.svg     |  66 +++++++++++
 .../data/icons/view-top-screen-symbolic.svg        |  66 +++++++++++
 plugins/nintendo-3ds/data/meson.build              |   7 ++
 .../nintendo-3ds/data/nintendo-3ds.gresource.xml   |  13 +++
 plugins/nintendo-3ds/data/nintendo-3ds.plugin      |   6 +
 .../data/ui/nintendo-3ds-layout-item.ui            |  45 ++++++++
 .../data/ui/nintendo-3ds-layout-switcher.ui        |  79 +++++++++++++
 plugins/nintendo-3ds/meson.build                   |   2 +
 plugins/nintendo-3ds/src/meson.build               |  21 ++++
 plugins/nintendo-3ds/src/nintendo-3ds-header.vala  |  58 ++++++++++
 .../nintendo-3ds/src/nintendo-3ds-layout-item.vala |  30 +++++
 .../src/nintendo-3ds-layout-switcher.vala          |  88 +++++++++++++++
 plugins/nintendo-3ds/src/nintendo-3ds-layout.vala  | 124 +++++++++++++++++++++
 plugins/nintendo-3ds/src/nintendo-3ds-plugin.vala  |  72 ++++++++++++
 plugins/nintendo-3ds/src/nintendo-3ds-runner.vala  | 120 ++++++++++++++++++++
 plugins/nintendo-ds/src/nintendo-ds-runner.vala    |   1 -
 src/core/snapshot-manager.vala                     |   3 +
 src/retro/retro-runner.vala                        |  38 ++++++-
 src/utils/file-operations.vala                     |   2 +-
 30 files changed, 1165 insertions(+), 4 deletions(-)
---
diff --git a/data/options/citra.options b/data/options/citra.options
new file mode 100644
index 00000000..1f3fa6ff
--- /dev/null
+++ b/data/options/citra.options
@@ -0,0 +1,2 @@
+[Options]
+citra_analog_function=C-Stick
diff --git a/data/options/meson.build b/data/options/meson.build
index 3b1278f7..f0228111 100644
--- a/data/options/meson.build
+++ b/data/options/meson.build
@@ -1,4 +1,5 @@
 options_files = [
+  'citra.options',
   'desmume.options',
   'desmume2015.options',
   'flycast.options',
diff --git a/data/org.gnome.Games.desktop.in.in b/data/org.gnome.Games.desktop.in.in
index 779e780a..17c9a470 100644
--- a/data/org.gnome.Games.desktop.in.in
+++ b/data/org.gnome.Games.desktop.in.in
@@ -13,4 +13,4 @@ Type=Application
 StartupNotify=true
 DBusActivatable=true
 Categories=GNOME;GTK;Player;Game;
-MimeType=application/vnd.nintendo.snes.rom;application/x-amiga-disk-format;application/x-atari-2600-rom;application/x-atari-7800-rom;application/x-atari-lynx-rom;application/x-cue;application/x-discjuggler-cd-image;application/x-doom-wad;application/x-fds-disk;application/x-gameboy-color-rom;application/x-gameboy-rom;application/x-gamecube-rom;application/x-gamegear-rom;application/x-gba-rom;application/x-gd-rom-cue;application/x-genesis-32x-rom;application/x-genesis-rom;application/x-love-game;application/x-mame-rom;application/x-ms-dos-executable;application/x-n64-rom;application/x-neo-geo-pocket-color-rom;application/x-neo-geo-pocket-rom;application/x-nes-rom;application/x-nintendo-ds-rom;application/x-pc-engine-rom;application/x-playstation-rom;application/x-saturn-rom;application/x-sega-cd-rom;application/x-sega-pico-rom;application/x-sg1000-rom;application/x-sms-rom;application/x-virtual-boy-rom;application/x-wii-rom;application/x-wii-wad;application/x-wonderswan-color-rom;app
 lication/x-wonderswan-rom;application/zip;
+MimeType=application/vnd.nintendo.snes.rom;application/x-amiga-disk-format;application/x-atari-2600-rom;application/x-atari-7800-rom;application/x-atari-lynx-rom;application/x-cue;application/x-dc-rom;application/x-doom-wad;application/x-fds-disk;application/x-gameboy-color-rom;application/x-gameboy-rom;application/x-gamecube-rom;application/x-gamegear-rom;application/x-gba-rom;application/x-genesis-32x-rom;application/x-genesis-rom;application/x-love-game;application/x-mame-rom;application/x-ms-dos-executable;application/x-n64-rom;application/x-neo-geo-pocket-color-rom;application/x-neo-geo-pocket-rom;application/x-nes-rom;application/x-nintendo-3ds-executable;application/x-nintendo-3ds-rom;application/x-nintendo-ds-rom;application/x-pc-engine-rom;application/x-playstation-rom;application/x-saturn-rom;application/x-sega-cd-rom;application/x-sega-pico-rom;application/x-sg1000-rom;application/x-sms-rom;application/x-virtual-boy-rom;application/x-wii-rom;application/x-wii-wad;applicat
 ion/x-wonderswan-color-rom;application/x-wonderswan-rom;application/zip;application/x-nintendo-3ds-rom;
diff --git a/flatpak/libretro-cores/citra.libretro b/flatpak/libretro-cores/citra.libretro
new file mode 100644
index 00000000..608cf2bd
--- /dev/null
+++ b/flatpak/libretro-cores/citra.libretro
@@ -0,0 +1,11 @@
+[Libretro]
+Type=Emulator
+Version=1.0
+Name=Citra
+Module=citra_libretro.so
+LibretroVersion=1
+Authors=Citra Emulation Project;
+License=GPL-2.0+;
+
+[Platform:Nintendo3DS]
+MimeType=application/x-nintendo-3ds-rom;application/x-nintendo-3ds-executable;
diff --git a/flatpak/libretro-cores/libretro-citra.json b/flatpak/libretro-cores/libretro-citra.json
new file mode 100644
index 00000000..89cd7a92
--- /dev/null
+++ b/flatpak/libretro-cores/libretro-citra.json
@@ -0,0 +1,32 @@
+        {
+            "name": "libretro-citra",
+            "buildsystem": "cmake",
+            "config-opts": [
+                "-DENABLE_LIBRETRO=1",
+                "-DLIBRETRO_STATIC=1",
+                "-DENABLE_SDL2=0",
+                "-DENABLE_QT=0",
+                "-DCMAKE_BUILD_TYPE=Release",
+                "-DENABLE_WEB_SERVICE=0"
+            ],
+            "make-args": [
+                "--target",
+                "citra_libretro"
+            ],
+            "post-install": [
+                /* TODO: Send that upstream */
+                "mkdir -p /app/lib/libretro/",
+                "install -m644 -p citra_libretro.so /app/lib/libretro/",
+                "install -m644 -p citra.libretro /app/lib/libretro/"
+            ],
+            "sources": [
+                {
+                    "type": "git",
+                    "url": "https://github.com/libretro/citra.git";
+                },
+                {
+                    "type": "file",
+                    "path": "citra.libretro"
+                }
+            ]
+        }
diff --git a/meson_options.txt b/meson_options.txt
index fa589741..1012b464 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -17,6 +17,7 @@ option ('libretro-plugin',      description: 'Support for Libretro games',
 option ('love-plugin',          description: 'Support for LÖVE games',          type: 'boolean')
 option ('mame-plugin',          description: 'Support for MAME games',          type: 'boolean')
 option ('ms-dos-plugin',        description: 'Support for MS-DOS games',        type: 'boolean')
+option ('nintendo-3ds-plugin',  description: 'Support for Nintendo 3DS games',  type: 'boolean')
 option ('nintendo-64-plugin',   description: 'Support for Nintendo 64 games',   type: 'boolean')
 option ('nintendo-ds-plugin',   description: 'Support for Nintendo DS games',   type: 'boolean')
 option ('playstation-plugin',   description: 'Support for PlayStation games',   type: 'boolean')
diff --git a/plugins/meson.build b/plugins/meson.build
index f15025bb..015da414 100644
--- a/plugins/meson.build
+++ b/plugins/meson.build
@@ -6,6 +6,7 @@ plugins = [
   'love',
   'mame',
   'ms-dos',
+  'nintendo-3ds',
   'nintendo-64',
   'nintendo-ds',
   'playstation',
diff --git a/plugins/nintendo-3ds/data/icons/screen-layout-left-right-symbolic.svg 
b/plugins/nintendo-3ds/data/icons/screen-layout-left-right-symbolic.svg
new file mode 100644
index 00000000..66c963f4
--- /dev/null
+++ b/plugins/nintendo-3ds/data/icons/screen-layout-left-right-symbolic.svg
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="16"
+   height="16"
+   version="1.1"
+   id="svg8"
+   sodipodi:docname="ds-touchscreen-right.svg"
+   inkscape:version="0.92.2 2405546, 2018-03-11">
+  <metadata
+     id="metadata14">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="2560"
+     inkscape:window-height="1376"
+     id="namedview10"
+     showgrid="true"
+     inkscape:zoom="64"
+     inkscape:cx="3.6767386"
+     inkscape:cy="8.1664307"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg8">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4526" />
+  </sodipodi:namedview>
+  <g
+     id="g873">
+    <rect
+       y="5.5"
+       x="7.5"
+       height="6"
+       width="6"
+       id="rect869"
+       
style="opacity:0.5;fill:#474747;fill-opacity:1;stroke:none;stroke-width:1.2544198;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
 />
+    <path
+       sodipodi:nodetypes="ssssssssscccccccccc"
+       inkscape:connector-curvature="0"
+       id="path4659-5"
+       d="M 15,6 C 15,5.057191 13.942809,4 13,4 H 2 C 1.057191,4 0,5.057191 0,6 v 5 c 0,0.942809 1.057191,2 
2,2 h 11 c 0.942809,0 2,-1.057191 2,-2 z m -2,0 v 5 H 8 V 6 Z M 7,6 v 5 H 2 V 6 Z"
+       
style="fill:#474747;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
 />
+  </g>
+</svg>
diff --git a/plugins/nintendo-3ds/data/icons/screen-layout-quick-switch-symbolic.svg 
b/plugins/nintendo-3ds/data/icons/screen-layout-quick-switch-symbolic.svg
new file mode 100644
index 00000000..f2b61d32
--- /dev/null
+++ b/plugins/nintendo-3ds/data/icons/screen-layout-quick-switch-symbolic.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="16"
+   height="16"
+   version="1.1"
+   id="svg8"
+   sodipodi:docname="screen-layout-quick-switch-symbolic.svg"
+   inkscape:version="0.92.4 5da689c313, 2019-01-14">
+  <metadata
+     id="metadata14">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1016"
+     id="namedview10"
+     showgrid="true"
+     inkscape:zoom="32"
+     inkscape:cx="8.3953872"
+     inkscape:cy="9.5898251"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg8">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4526" />
+  </sodipodi:namedview>
+  <path
+     
style="opacity:0.5;fill:#474747;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     d="m 9,1 c 0.942809,0 1.999979,1.057191 2,2 V 4 H 9 V 3 H 3 v 6 h 1 v 2 H 3 C 2.057191,11 
1.000021,9.942809 1,9 V 3 C 0.999979,2.057191 2.057191,1 3,1 Z"
+     id="path872"
+     inkscape:connector-curvature="0"
+     sodipodi:nodetypes="sccccccccsccss" />
+  <path
+     sodipodi:nodetypes="sccssccssccccc"
+     inkscape:connector-curvature="0"
+     id="path817"
+     d="m 13,5 c 0.942809,0 1.999979,1.057191 2,2 v 6 c 2.1e-5,0.942809 -1.057191,2 -2,2 H 7 C 6.057191,15 
5.000021,13.942809 5,13 V 7 C 4.999979,6.057191 6.057191,5 7,5 Z m 0,2 H 7 v 6 h 6 z"
+     
style="fill:#474747;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
 />
+</svg>
diff --git a/plugins/nintendo-3ds/data/icons/screen-layout-right-left-symbolic.svg 
b/plugins/nintendo-3ds/data/icons/screen-layout-right-left-symbolic.svg
new file mode 100644
index 00000000..0219572b
--- /dev/null
+++ b/plugins/nintendo-3ds/data/icons/screen-layout-right-left-symbolic.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="16"
+   height="16"
+   version="1.1"
+   id="svg8"
+   sodipodi:docname="ds-touchscreen-left.svg"
+   inkscape:version="0.92.2 2405546, 2018-03-11">
+  <metadata
+     id="metadata14">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="2560"
+     inkscape:window-height="1376"
+     id="namedview10"
+     showgrid="true"
+     inkscape:zoom="64"
+     inkscape:cx="3.6767386"
+     inkscape:cy="8.1664307"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg8">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4526" />
+  </sodipodi:namedview>
+  <g
+     id="g873"
+     transform="matrix(-1,0,0,1,15,0)">
+    <rect
+       y="5.5"
+       x="7.5"
+       height="6"
+       width="6"
+       id="rect869"
+       
style="opacity:0.5;fill:#474747;fill-opacity:1;stroke:none;stroke-width:1.2544198;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
 />
+    <path
+       sodipodi:nodetypes="ssssssssscccccccccc"
+       inkscape:connector-curvature="0"
+       id="path4659-5"
+       d="M 15,6 C 15,5.057191 13.942809,4 13,4 H 2 C 1.057191,4 0,5.057191 0,6 v 5 c 0,0.942809 1.057191,2 
2,2 h 11 c 0.942809,0 2,-1.057191 2,-2 z m -2,0 v 5 H 8 V 6 Z M 7,6 v 5 H 2 V 6 Z"
+       
style="fill:#474747;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
 />
+  </g>
+</svg>
diff --git a/plugins/nintendo-3ds/data/icons/screen-layout-top-bottom-symbolic.svg 
b/plugins/nintendo-3ds/data/icons/screen-layout-top-bottom-symbolic.svg
new file mode 100644
index 00000000..5f633d76
--- /dev/null
+++ b/plugins/nintendo-3ds/data/icons/screen-layout-top-bottom-symbolic.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="16"
+   height="16"
+   version="1.1"
+   id="svg8"
+   sodipodi:docname="ds-touchscreen-bottom.svg"
+   inkscape:version="0.92.2 2405546, 2018-03-11">
+  <metadata
+     id="metadata14">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="2560"
+     inkscape:window-height="1376"
+     id="namedview10"
+     showgrid="true"
+     inkscape:zoom="64"
+     inkscape:cx="3.6767386"
+     inkscape:cy="8.1664307"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg8">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4526" />
+  </sodipodi:namedview>
+  <g
+     id="g873"
+     transform="matrix(0,1,1,0,-1,0)">
+    <rect
+       y="5.5"
+       x="7.5"
+       height="6"
+       width="6"
+       id="rect869"
+       
style="opacity:0.5;fill:#474747;fill-opacity:1;stroke:none;stroke-width:1.2544198;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
 />
+    <path
+       sodipodi:nodetypes="ssssssssscccccccccc"
+       inkscape:connector-curvature="0"
+       id="path4659-5"
+       d="M 15,6 C 15,5.057191 13.942809,4 13,4 H 2 C 1.057191,4 0,5.057191 0,6 v 5 c 0,0.942809 1.057191,2 
2,2 h 11 c 0.942809,0 2,-1.057191 2,-2 z m -2,0 v 5 H 8 V 6 Z M 7,6 v 5 H 2 V 6 Z"
+       
style="fill:#474747;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
 />
+  </g>
+</svg>
diff --git a/plugins/nintendo-3ds/data/icons/view-bottom-screen-symbolic.svg 
b/plugins/nintendo-3ds/data/icons/view-bottom-screen-symbolic.svg
new file mode 100644
index 00000000..fda93dbb
--- /dev/null
+++ b/plugins/nintendo-3ds/data/icons/view-bottom-screen-symbolic.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="16"
+   height="16"
+   version="1.1"
+   id="svg8"
+   sodipodi:docname="6-bottom.svg"
+   inkscape:version="0.92.4 5da689c313, 2019-01-14">
+  <metadata
+     id="metadata14">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1016"
+     id="namedview10"
+     showgrid="true"
+     inkscape:zoom="32"
+     inkscape:cx="1.5549575"
+     inkscape:cy="8.7612104"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg8">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4526" />
+  </sodipodi:namedview>
+  <path
+     sodipodi:nodetypes="sccssccssscscccscscccs"
+     inkscape:connector-curvature="0"
+     id="path858"
+     d="m 9,1 c 0.942809,0 1.999979,1.057191 2,2 v 6 c 2.1e-5,0.942809 -1.057191,2 -2,2 H 3 C 2.057191,11 
1.000021,9.942809 1,9 V 3 C 0.999979,2.057191 2.057191,1 3,1 Z M 4,4 H 3 V 5 C 3,5.31 3.09025,5.552 
3.28125,5.75 L 6,8.4082031 8.71875,5.751953 C 8.90875,5.553953 9,5.311953 9,5.001953 v -1 H 8 c -0.257,0 
-0.52775,0.1295 -0.71875,0.3125 L 6,5.59375 4.71875,4.314453 C 4.52875,4.131453 4.257,4 4,4 Z"
+     
style="fill:#474747;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
 />
+  <path
+     inkscape:connector-curvature="0"
+     id="path818"
+     d="M 7,15 C 6.057191,15 5.000021,13.942809 5,13 v -1 h 2 v 1 h 6 V 7 H 12 V 5 h 1 c 0.942809,0 
1.999979,1.057191 2,2 v 6 c 2.1e-5,0.942809 -1.057191,2 -2,2 z"
+     
style="opacity:0.5;fill:#474747;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     sodipodi:nodetypes="sccccccccsccss" />
+</svg>
diff --git a/plugins/nintendo-3ds/data/icons/view-top-screen-symbolic.svg 
b/plugins/nintendo-3ds/data/icons/view-top-screen-symbolic.svg
new file mode 100644
index 00000000..d9a3b696
--- /dev/null
+++ b/plugins/nintendo-3ds/data/icons/view-top-screen-symbolic.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="16"
+   height="16"
+   version="1.1"
+   id="svg8"
+   sodipodi:docname="6-top.svg"
+   inkscape:version="0.92.4 5da689c313, 2019-01-14">
+  <metadata
+     id="metadata14">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1016"
+     id="namedview10"
+     showgrid="true"
+     inkscape:zoom="32"
+     inkscape:cx="1.5549575"
+     inkscape:cy="8.7612104"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg8">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4526" />
+  </sodipodi:namedview>
+  <path
+     sodipodi:nodetypes="sccssccssscscccscscccs"
+     inkscape:connector-curvature="0"
+     id="path858"
+     d="M 7,15 C 6.057191,15 5.000021,13.942809 5,13 V 7 C 4.999979,6.057191 6.057191,4.9999999 7,4.9999999 
h 6 c 0.942809,0 1.999979,1.0571911 2,2.0000001 v 6 c 2.1e-5,0.942809 -1.057191,2 -2,2 z m 5,-3 h 1 v -1 c 
0,-0.31 -0.09025,-0.552 -0.28125,-0.75 L 10,7.5917969 7.28125,10.248047 c -0.19,0.198 -0.28125,0.44 
-0.28125,0.75 v 1 h 1 c 0.257,0 0.52775,-0.1295 0.71875,-0.3125 L 10,10.40625 11.28125,11.685547 C 
11.47125,11.868547 11.743,12 12,12 Z"
+     
style="fill:#474747;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
 />
+  <path
+     inkscape:connector-curvature="0"
+     id="path818"
+     d="m 9,1 c 0.942809,0 1.999979,1.057191 2,2 V 4 H 9 V 3 H 3 v 6 h 1 v 2 H 3 C 2.057191,11 
1.000021,9.942809 1,9 V 3 C 0.999979,2.057191 2.057191,1 3,1 Z"
+     
style="opacity:0.5;fill:#474747;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     sodipodi:nodetypes="sccccccccsccss" />
+</svg>
diff --git a/plugins/nintendo-3ds/data/meson.build b/plugins/nintendo-3ds/data/meson.build
new file mode 100644
index 00000000..a63a87eb
--- /dev/null
+++ b/plugins/nintendo-3ds/data/meson.build
@@ -0,0 +1,7 @@
+install_data (plugin_name + '.plugin', install_dir: plugins_dir)
+
+nintendo_3ds_resources = gnome.compile_resources (
+  'nintendo-3ds',
+  'nintendo-3ds.gresource.xml',
+  c_name: 'resources'
+)
diff --git a/plugins/nintendo-3ds/data/nintendo-3ds.gresource.xml 
b/plugins/nintendo-3ds/data/nintendo-3ds.gresource.xml
new file mode 100644
index 00000000..2e0900c6
--- /dev/null
+++ b/plugins/nintendo-3ds/data/nintendo-3ds.gresource.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/Games/plugins/nintendo-3ds">
+    <file>ui/nintendo-3ds-layout-item.ui</file>
+    <file>ui/nintendo-3ds-layout-switcher.ui</file>
+    <file>icons/screen-layout-left-right-symbolic.svg</file>
+    <file>icons/screen-layout-quick-switch-symbolic.svg</file>
+    <file>icons/screen-layout-right-left-symbolic.svg</file>
+    <file>icons/screen-layout-top-bottom-symbolic.svg</file>
+    <file>icons/view-bottom-screen-symbolic.svg</file>
+    <file>icons/view-top-screen-symbolic.svg</file>
+  </gresource>
+</gresources>
diff --git a/plugins/nintendo-3ds/data/nintendo-3ds.plugin b/plugins/nintendo-3ds/data/nintendo-3ds.plugin
new file mode 100644
index 00000000..f3edf634
--- /dev/null
+++ b/plugins/nintendo-3ds/data/nintendo-3ds.plugin
@@ -0,0 +1,6 @@
+[Plugin]
+Module=libgames-nintendo-3ds-plugin
+Name=Nintendo 3DS Plugin
+Description=Provides support for Nintendo 3DS games.
+Authors=Alexander Mikhaylenko <alexm gnome org>
+Copyright=Copyright © 2020 Alexander Mikhaylenko
diff --git a/plugins/nintendo-3ds/data/ui/nintendo-3ds-layout-item.ui 
b/plugins/nintendo-3ds/data/ui/nintendo-3ds-layout-item.ui
new file mode 100644
index 00000000..96b9988f
--- /dev/null
+++ b/plugins/nintendo-3ds/data/ui/nintendo-3ds-layout-item.ui
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.24"/>
+  <template class="GamesNintendo3DsLayoutItem" parent="GtkListBoxRow">
+    <property name="visible">True</property>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="margin">6</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkImage" id="icon">
+            <property name="visible">True</property>
+            <property name="pixel-size">32</property>
+            <style>
+              <class name="list-icon"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <property name="valign">center</property>
+            <child>
+              <object class="GtkLabel" id="title">
+                <property name="visible">True</property>
+                <property name="halign">start</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="subtitle">
+                <property name="visible">False</property>
+                <property name="halign">start</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/plugins/nintendo-3ds/data/ui/nintendo-3ds-layout-switcher.ui 
b/plugins/nintendo-3ds/data/ui/nintendo-3ds-layout-switcher.ui
new file mode 100644
index 00000000..ff180127
--- /dev/null
+++ b/plugins/nintendo-3ds/data/ui/nintendo-3ds-layout-switcher.ui
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.24"/>
+  <template class="GamesNintendo3DsLayoutSwitcher" parent="GtkBox">
+    <property name="visible">True</property>
+
+    <child>
+      <object class="GtkRevealer" id="change_screen_revealer">
+        <property name="visible">True</property>
+        <property name="transition-type">slide-left</property>
+        <child>
+          <object class="GtkButton">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="margin-end">6</property>
+            <signal name="clicked" handler="on_screen_changed"/>
+            <child internal-child="accessible">
+              <object class="AtkObject">
+                <property name="accessible-name" translatable="yes">Change Screen</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkImage" id="change_screen_image">
+                <property name="visible">True</property>
+              </object>
+            </child>
+           </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkMenuButton" id="layout_button">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="popover">layout_popover</property>
+        <signal name="notify::active" handler="on_menu_state_changed"/>
+          <child internal-child="accessible">
+          <object class="AtkObject" id="a11y-display-discs">
+            <property name="accessible-name" translatable="yes">Screen Layout</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkImage" id="layout_image">
+                <property name="visible">True</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="icon-name">pan-down-symbolic</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkPopover" id="layout_popover">
+    <property name="visible">False</property>
+    <signal name="show" handler="update_ui"/>
+    <child>
+      <object class="GtkFrame">
+        <property name="visible">True</property>
+        <property name="margin">6</property>
+        <property name="shadow-type">in</property>
+        <child>
+          <object class="GtkListBox" id="list_box">
+            <property name="visible">True</property>
+            <signal name="row-activated" handler="on_row_activated"/>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/plugins/nintendo-3ds/meson.build b/plugins/nintendo-3ds/meson.build
new file mode 100644
index 00000000..d7e84900
--- /dev/null
+++ b/plugins/nintendo-3ds/meson.build
@@ -0,0 +1,2 @@
+subdir ('data')
+subdir ('src')
diff --git a/plugins/nintendo-3ds/src/meson.build b/plugins/nintendo-3ds/src/meson.build
new file mode 100644
index 00000000..c55f1f0e
--- /dev/null
+++ b/plugins/nintendo-3ds/src/meson.build
@@ -0,0 +1,21 @@
+vala_sources = [
+  'nintendo-3ds-header.vala',
+  'nintendo-3ds-layout.vala',
+  'nintendo-3ds-layout-item.vala',
+  'nintendo-3ds-layout-switcher.vala',
+  'nintendo-3ds-plugin.vala',
+  'nintendo-3ds-runner.vala',
+]
+
+c_args = [
+  '-DG_LOG_DOMAIN="GamesNintendo3DS"'
+]
+
+shared_module (
+  'games-' + plugin_name + '-plugin',
+  vala_sources + nintendo_3ds_resources,
+  dependencies: gnome_games_dep,
+  c_args: c_args,
+  install: true,
+  install_dir: plugins_dir
+)
diff --git a/plugins/nintendo-3ds/src/nintendo-3ds-header.vala 
b/plugins/nintendo-3ds/src/nintendo-3ds-header.vala
new file mode 100644
index 00000000..868c74ef
--- /dev/null
+++ b/plugins/nintendo-3ds/src/nintendo-3ds-header.vala
@@ -0,0 +1,58 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+// Documentation: https://www.planetvb.com/content/downloads/documents/stsvb.html
+private class Games.Nintendo3DsHeader : Object {
+       private const size_t MAGIC_OFFSET = 0x189;
+       private const uint8 MAGIC_VALUE = 0;
+
+       private File file;
+
+       public Nintendo3DsHeader (File file) {
+               this.file = file;
+       }
+
+       public void check_validity () throws Nintendo3DSError {
+
+
+               var stream = get_stream ();
+               ssize_t read = 0;
+
+               try {
+                       stream.seek (MAGIC_OFFSET, SeekType.SET);
+               }
+               catch (Error e) {
+                       throw new Nintendo3DSError.INVALID_SIZE ("Invalid Nintendo 3DS ROM header size: %s", 
e.message);
+               }
+
+               var buffer = new uint8[1];
+               try {
+                       read = stream.read (buffer);
+               }
+               catch (Error e) {
+                       throw new Nintendo3DSError.INVALID_SIZE (e.message);
+               }
+
+               if (read < 1)
+                       throw new Nintendo3DSError.INVALID_SIZE ("Invalid Nintendo 3DS ROM header size.");
+
+               print ("WTF %s %d\n", file.get_path (), buffer[0]);
+               if (buffer[0] != MAGIC_VALUE)
+                       throw new Nintendo3DSError.ROM_ENCRYPTED ("The ROM is encrypted.");
+       }
+
+       private FileInputStream get_stream () throws Nintendo3DSError {
+               try {
+                       return file.read ();
+               }
+               catch (Error e) {
+                       throw new Nintendo3DSError.CANT_READ_FILE ("Couldn’t read file: %s", e.message);
+               }
+       }
+}
+
+errordomain Games.Nintendo3DSError {
+       CANT_READ_FILE,
+       INVALID_FILE,
+       INVALID_SIZE,
+       ROM_ENCRYPTED,
+}
diff --git a/plugins/nintendo-3ds/src/nintendo-3ds-layout-item.vala 
b/plugins/nintendo-3ds/src/nintendo-3ds-layout-item.vala
new file mode 100644
index 00000000..f546f563
--- /dev/null
+++ b/plugins/nintendo-3ds/src/nintendo-3ds-layout-item.vala
@@ -0,0 +1,30 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+[GtkTemplate (ui = "/org/gnome/Games/plugins/nintendo-3ds/ui/nintendo-3ds-layout-item.ui")]
+private class Games.Nintendo3DsLayoutItem : Gtk.ListBoxRow {
+       [GtkChild]
+       private Gtk.Image icon;
+       [GtkChild]
+       private Gtk.Label title;
+       [GtkChild]
+       private Gtk.Label subtitle;
+
+       public Nintendo3DsLayout layout { get; construct; }
+
+       public Nintendo3DsLayoutItem (Nintendo3DsLayout layout) {
+               Object (layout: layout);
+       }
+
+       public override void constructed () {
+               icon.icon_name = layout.get_icon ();
+               title.label = layout.get_title ();
+
+               var subtitle_str = layout.get_subtitle ();
+               if (subtitle_str != null) {
+                       subtitle.label = subtitle_str;
+                       subtitle.show ();
+               }
+
+               base.constructed ();
+       }
+}
diff --git a/plugins/nintendo-3ds/src/nintendo-3ds-layout-switcher.vala 
b/plugins/nintendo-3ds/src/nintendo-3ds-layout-switcher.vala
new file mode 100644
index 00000000..8d28b485
--- /dev/null
+++ b/plugins/nintendo-3ds/src/nintendo-3ds-layout-switcher.vala
@@ -0,0 +1,88 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+[GtkTemplate (ui = "/org/gnome/Games/plugins/nintendo-3ds/ui/nintendo-3ds-layout-switcher.ui")]
+private class Games.Nintendo3DsLayoutSwitcher : Gtk.Box, HeaderBarWidget {
+       [GtkChild]
+       private Gtk.Revealer change_screen_revealer;
+       [GtkChild]
+       private Gtk.Image change_screen_image;
+       [GtkChild]
+       private Gtk.MenuButton layout_button;
+       [GtkChild]
+       private Gtk.Image layout_image;
+       [GtkChild]
+       private Gtk.Popover layout_popover;
+       [GtkChild]
+       private Gtk.ListBox list_box;
+
+       private HashTable<Nintendo3DsLayout, Nintendo3DsLayoutItem> items;
+
+       public Nintendo3DsRunner runner { get; construct; }
+
+       private bool is_menu_open;
+       public bool block_autohide {
+               get { return is_menu_open; }
+       }
+
+       static construct {
+               var icon_theme = Gtk.IconTheme.get_default ();
+               icon_theme.add_resource_path ("/org/gnome/Games/plugins/nintendo-3ds/icons");
+       }
+
+       public override void constructed () {
+               items = new HashTable<Nintendo3DsLayout, Nintendo3DsLayoutItem> (direct_hash, direct_equal);
+               foreach (var layout in Nintendo3DsLayout.get_layouts ()) {
+                       var item = new Nintendo3DsLayoutItem (layout);
+
+                       items[layout] = item;
+                       list_box.add (item);
+               }
+
+               update_ui ();
+
+               runner.notify["screen-layout"].connect (update_ui);
+               runner.notify["view-bottom-screen"].connect (update_ui);
+
+               base.constructed ();
+       }
+
+       public Nintendo3DsLayoutSwitcher (Nintendo3DsRunner runner) {
+               Object (runner: runner);
+       }
+
+       [GtkCallback]
+       private void on_menu_state_changed () {
+               is_menu_open = layout_button.active;
+               notify_property ("block-autohide");
+       }
+
+       [GtkCallback]
+       private void update_ui () {
+               var layout = runner.screen_layout;
+               var view_bottom = runner.view_bottom_screen;
+
+               layout_image.icon_name = layout.get_icon ();
+
+               var item = items[layout];
+               list_box.select_row (item);
+
+               change_screen_revealer.reveal_child = (layout == Nintendo3DsLayout.QUICK_SWITCH);
+               change_screen_image.icon_name = view_bottom ?
+                                               "view-top-screen-symbolic" :
+                                               "view-bottom-screen-symbolic";
+       }
+
+       [GtkCallback]
+       private void on_screen_changed (Gtk.Button button) {
+               runner.view_bottom_screen = !runner.view_bottom_screen;
+       }
+
+       [GtkCallback]
+       private void on_row_activated (Gtk.ListBoxRow row) {
+               var layout_item = row as Nintendo3DsLayoutItem;
+
+               runner.screen_layout = layout_item.layout;
+
+               layout_popover.popdown ();
+       }
+}
diff --git a/plugins/nintendo-3ds/src/nintendo-3ds-layout.vala 
b/plugins/nintendo-3ds/src/nintendo-3ds-layout.vala
new file mode 100644
index 00000000..759e47a7
--- /dev/null
+++ b/plugins/nintendo-3ds/src/nintendo-3ds-layout.vala
@@ -0,0 +1,124 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+public enum Games.Nintendo3DsLayout {
+       TOP_BOTTOM,
+       LEFT_RIGHT,
+       RIGHT_LEFT,
+       QUICK_SWITCH;
+
+       public string get_value () {
+               switch (this) {
+               case TOP_BOTTOM:
+                       return "Default Top-Bottom Screen";
+
+               case LEFT_RIGHT:
+               case RIGHT_LEFT:
+                       return "Side by Side";
+
+               case QUICK_SWITCH:
+                       return "Single Screen Only";
+
+               default:
+                       assert_not_reached ();
+               }
+       }
+
+       public string get_icon () {
+               switch (this) {
+               case TOP_BOTTOM:
+                       return "screen-layout-top-bottom-symbolic";
+
+               case LEFT_RIGHT:
+                       return "screen-layout-left-right-symbolic";
+
+               case RIGHT_LEFT:
+                       return "screen-layout-right-left-symbolic";
+
+               case QUICK_SWITCH:
+                       return "screen-layout-quick-switch-symbolic";
+
+               default:
+                       assert_not_reached ();
+               }
+       }
+
+       public string get_title () {
+               switch (this) {
+               case TOP_BOTTOM:
+                       /* Translators: This describes the layout for the Nintendo DS
+                        * emulator. This setting means the two screens are stacked one on
+                        * top of the other */
+                       return _("Vertical");
+
+               case LEFT_RIGHT:
+                       /* Translators: This describes the layout for the Nintendo DS
+                        * emulator. This setting means the two screens are displayed side
+                        * by side */
+                       return _("Side by side");
+
+               case RIGHT_LEFT:
+                       /* Translators: This describes the layout for the Nintendo DS
+                        * emulator. This setting means the two screens are displayed side
+                        * by side */
+                       return _("Side by side");
+
+               case QUICK_SWITCH:
+                       /* Translators: This describes the layout for the Nintendo DS
+                        * emulator. This setting means only one screen is displayed at
+                        * once. The screen displayed can then be changed in-game. */
+                       return _("Single screen");
+
+               default:
+                       assert_not_reached ();
+               }
+       }
+
+       public string? get_subtitle () {
+               switch (this) {
+               case LEFT_RIGHT:
+                       /* Translators: This describes the layout for the Nintendo DS
+                        * emulator when the two screens are displayed side by side and not
+                        * one on top of the other. The bottom screen is displayed to the
+                        * right of the top screen. */
+                       return _("Bottom to the right");
+
+               case RIGHT_LEFT:
+                       /* Translators: This describes the layout for the Nintendo DS
+                        * emulator when the two screens are displayed side by side and not
+                        * one on top of the other. The bottom screen is displayed to the
+                        * left of the top screen. */
+                       return _("Bottom to the left");
+
+               case TOP_BOTTOM:
+               case QUICK_SWITCH:
+                       return null;
+
+               default:
+                       assert_not_reached ();
+               }
+       }
+
+       public static Nintendo3DsLayout[] get_layouts () {
+               return { TOP_BOTTOM, LEFT_RIGHT, RIGHT_LEFT, QUICK_SWITCH };
+       }
+
+       public static Nintendo3DsLayout? from_value (string value) {
+               switch (value) {
+               case "top/bottom":
+                       return TOP_BOTTOM;
+
+               case "left/right":
+                       return LEFT_RIGHT;
+
+               case "right/left":
+                       return RIGHT_LEFT;
+
+               case "quick switch":
+                       return QUICK_SWITCH;
+
+               default:
+                       warning ("Unknown screen layout: %s\n", value);
+                       return null;
+               }
+       }
+}
diff --git a/plugins/nintendo-3ds/src/nintendo-3ds-plugin.vala 
b/plugins/nintendo-3ds/src/nintendo-3ds-plugin.vala
new file mode 100644
index 00000000..08a6053d
--- /dev/null
+++ b/plugins/nintendo-3ds/src/nintendo-3ds-plugin.vala
@@ -0,0 +1,72 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+private class Games.Nintendo3DsPlugin : Object, Plugin {
+       private const string MIME_TYPE = "application/x-nintendo-3ds-rom";
+       private const string MIME_TYPE_3DSX = "application/x-nintendo-3ds-executable";
+       private const string PLATFORM_ID = "Nintendo3DS";
+       private const string PLATFORM_NAME = _("Nintendo 3DS");
+       private const string PLATFORM_UID_PREFIX = "nintendo-3ds";
+
+       private static RetroPlatform platform;
+
+       static construct {
+               platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, { MIME_TYPE, MIME_TYPE_3DSX }, 
PLATFORM_UID_PREFIX);
+       }
+
+       public Platform[] get_platforms () {
+               return { platform };
+       }
+
+       public string[] get_mime_types () {
+               return { MIME_TYPE, MIME_TYPE_3DSX };
+       }
+
+       public UriGameFactory[] get_uri_game_factories () {
+               var game_uri_adapter = new GenericGameUriAdapter (game_for_uri);
+               var factory = new GenericUriGameFactory (game_uri_adapter);
+               factory.add_mime_type (MIME_TYPE);
+               factory.add_mime_type (MIME_TYPE_3DSX);
+
+               return { factory };
+       }
+
+       public RunnerFactory[] get_runner_factories () {
+               var factory = new GenericRunnerFactory (create_runner);
+               factory.add_platform (platform);
+
+               return { factory };
+       }
+
+       private static Game game_for_uri (Uri uri) throws Error {
+               var file = uri.to_file ();
+
+               var file_info = file.query_info ("*", FileQueryInfoFlags.NONE);
+               if (file_info.get_content_type () == MIME_TYPE) {
+                       var header = new Nintendo3DsHeader (file);
+                       header.check_validity ();
+               }
+
+               var uid = new Uid (Fingerprint.get_uid (uri, PLATFORM_UID_PREFIX));
+               var title = new FilenameTitle (uri);
+               var media = new GriloMedia (title, MIME_TYPE);
+               var cover = new CompositeCover ({
+                       new LocalCover (uri),
+                       new GriloCover (media, uid)});
+
+               var game = new Game (uid, uri, title, platform);
+               game.set_cover (cover);
+
+               return game;
+       }
+
+       private static Runner? create_runner (Game game) throws Error {
+               var core_source = new RetroCoreSource (platform);
+
+               return new Nintendo3DsRunner (game, core_source);
+       }
+}
+
+[ModuleInit]
+public Type register_games_plugin (TypeModule module) {
+       return typeof (Games.Nintendo3DsPlugin);
+}
diff --git a/plugins/nintendo-3ds/src/nintendo-3ds-runner.vala 
b/plugins/nintendo-3ds/src/nintendo-3ds-runner.vala
new file mode 100644
index 00000000..7cad475d
--- /dev/null
+++ b/plugins/nintendo-3ds/src/nintendo-3ds-runner.vala
@@ -0,0 +1,120 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+private class Games.Nintendo3DsRunner : RetroRunner {
+       // Map the 1,2,3,4 key values to the 4 screen layouts of the Nintendo 3DS
+       private static HashTable<uint, Nintendo3DsLayout?> layouts;
+
+       private const string SCREENS_LAYOUT_OPTION = "citra_layout_option";
+       private const string PROMINENT_SCREEN_OPTION = "citra_swap_screen";
+
+       private const size_t HEADER_GAME_CODE_OFFSET = 12;
+       private const size_t HEADER_GAME_CODE_SIZE = 3;
+
+       private Nintendo3DsLayout _screen_layout;
+       public Nintendo3DsLayout screen_layout {
+               get { return _screen_layout; }
+               set {
+                       _screen_layout = value;
+                       update_screen_layout ();
+               }
+       }
+
+       private bool _view_bottom_screen;
+       public bool view_bottom_screen {
+               get { return _view_bottom_screen; }
+               set {
+                       _view_bottom_screen = value;
+                       update_screen_layout ();
+               }
+       }
+
+       static construct {
+               layouts = new HashTable<uint, Nintendo3DsLayout?> (direct_hash, direct_equal);
+
+               layouts[Gdk.Key.@1] = Nintendo3DsLayout.TOP_BOTTOM;
+               layouts[Gdk.Key.@2] = Nintendo3DsLayout.LEFT_RIGHT;
+               layouts[Gdk.Key.@3] = Nintendo3DsLayout.RIGHT_LEFT;
+               layouts[Gdk.Key.@4] = Nintendo3DsLayout.QUICK_SWITCH;
+       }
+
+       public Nintendo3DsRunner (Game game, RetroCoreSource source) {
+               base.from_source (game, source);
+       }
+
+       private bool core_supports_layouts () {
+               var core = get_core ();
+
+               return core != null && core.has_option (SCREENS_LAYOUT_OPTION) && core.has_option 
(PROMINENT_SCREEN_OPTION);
+       }
+
+       private void update_screen_layout () {
+               if (!core_supports_layouts ())
+                       return;
+
+               var core = get_core ();
+
+               var screens_layout_option = core.get_option (SCREENS_LAYOUT_OPTION);
+               var prominent_screen_option = core.get_option (PROMINENT_SCREEN_OPTION);
+
+               var screens_layout_option_value = screen_layout.get_value ();
+               bool use_bottom_screen = false;
+
+               if (screen_layout == Nintendo3DsLayout.RIGHT_LEFT)
+                       use_bottom_screen = true;
+
+               if (screen_layout == Nintendo3DsLayout.QUICK_SWITCH)
+                       use_bottom_screen = view_bottom_screen;
+
+               try {
+                       screens_layout_option.set_value (screens_layout_option_value);
+                       prominent_screen_option.set_value (use_bottom_screen ? "Bottom" : "Top");
+               }
+               catch (Error e) {
+                       critical ("Failed to set desmume option: %s", e.message);
+               }
+       }
+
+       public override HeaderBarWidget? get_extra_widget () {
+               if (!core_supports_layouts ())
+                       return null;
+
+               return new Nintendo3DsLayoutSwitcher (this);
+       }
+
+       public override bool key_press_event (uint keyval, Gdk.ModifierType state) {
+               if (state == Gdk.ModifierType.MOD1_MASK) {
+                       // Alt + 1|2|3|4
+                       var shortcut_layout = layouts[keyval];
+                       if (shortcut_layout != null) {
+                               screen_layout = shortcut_layout;
+
+                               return true;
+                       }
+               }
+
+               if (screen_layout != Nintendo3DsLayout.QUICK_SWITCH)
+                       return false;
+
+               var switch_keyval = view_bottom_screen ? Gdk.Key.Page_Up : Gdk.Key.Page_Down;
+               if (keyval == switch_keyval)
+                       return swap_screens ();
+
+               return false;
+       }
+
+       public override bool gamepad_button_press_event (uint16 button) {
+               if (button == EventCode.BTN_THUMBR)
+                       return swap_screens ();
+
+               return false;
+       }
+
+       private bool swap_screens () {
+               if (screen_layout != Nintendo3DsLayout.QUICK_SWITCH)
+                       return false;
+
+               view_bottom_screen = !view_bottom_screen;
+
+               return true;
+       }
+}
diff --git a/plugins/nintendo-ds/src/nintendo-ds-runner.vala b/plugins/nintendo-ds/src/nintendo-ds-runner.vala
index 0ac4a95b..6463adeb 100644
--- a/plugins/nintendo-ds/src/nintendo-ds-runner.vala
+++ b/plugins/nintendo-ds/src/nintendo-ds-runner.vala
@@ -76,7 +76,6 @@ private class Games.NintendoDsRunner : RetroRunner {
        }
 
        private string get_screen_gap_width () {
-
                try {
                        assert (media_set.get_size () == 1);
                        var uris = media_set.get_media (0).get_uris ();
diff --git a/src/core/snapshot-manager.vala b/src/core/snapshot-manager.vala
index ceea640b..652a35d7 100644
--- a/src/core/snapshot-manager.vala
+++ b/src/core/snapshot-manager.vala
@@ -30,6 +30,9 @@ public class Games.SnapshotManager : Object {
                string snapshot_name = null;
 
                while ((snapshot_name = dir.read_name ()) != null) {
+                       if (snapshot_name == "global")
+                               continue;
+
                        var snapshot_path = Path.build_filename (dir_path, snapshot_name);
                        snapshots += Snapshot.load (game.platform, core_id, snapshot_path);
                }
diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala
index 12114104..96bcbee8 100644
--- a/src/retro/retro-runner.vala
+++ b/src/retro/retro-runner.vala
@@ -218,7 +218,19 @@ public class Games.RetroRunner : Object, Runner {
                        var snapshot = snapshot_manager.get_latest_snapshot ();
 
                        tmp_save_dir = create_tmp_save_dir ();
-                       if (snapshot != null)
+                       if (!supports_snapshots) {
+                               var path = get_fallback_save_directory_path ();
+
+                               var save_ram_path = Path.build_filename (path, "save");
+                               var save_dir = File.new_for_path (Path.build_filename (path, "save-dir"));
+
+                               if (FileUtils.test (save_ram_path, FileTest.EXISTS) &&
+                                   core.get_memory_size (Retro.MemoryType.SAVE_RAM) > 0)
+                                       core.load_memory (Retro.MemoryType.SAVE_RAM, save_ram_path);
+
+                               FileOperations.copy_contents (save_dir, File.new_for_path (tmp_save_dir));
+                       }
+                       else if (snapshot != null)
                                snapshot.copy_save_dir_to (tmp_save_dir);
 
                        prepare_core ();
@@ -271,10 +283,34 @@ public class Games.RetroRunner : Object, Runner {
                running = false;
        }
 
+       private string get_fallback_save_directory_path () {
+               var uid = game.uid;
+               var core_id_prefix = get_core_id ().replace (".libretro", "");
+
+               return Path.build_filename (Application.get_data_dir (),
+                                           "savestates",
+                                           @"$uid-$core_id_prefix",
+                                           "global");
+       }
+
        public void stop () {
                if (!core_loaded)
                        return;
 
+               if (!supports_snapshots) {
+                       var path = get_fallback_save_directory_path ();
+
+                       if (core.get_memory_size (Retro.MemoryType.SAVE_RAM) > 0)
+                               core.save_memory (Retro.MemoryType.SAVE_RAM,
+                                                 Path.build_filename (path, "save"));
+
+                       var tmp_dir = File.new_for_path (tmp_save_dir);
+                       var dest_dir = File.new_for_path (Path.build_filename (path, "save-dir"));
+                       if (dest_dir.query_exists ())
+                               FileOperations.delete_files (dest_dir, {});
+                       FileOperations.copy_contents (tmp_dir, dest_dir);
+               }
+
                game.update_last_played ();
                deinit ();
                stopped ();
diff --git a/src/utils/file-operations.vala b/src/utils/file-operations.vala
index 7254c51f..c4a2f5cb 100644
--- a/src/utils/file-operations.vala
+++ b/src/utils/file-operations.vala
@@ -195,7 +195,7 @@ public class Games.FileOperations {
 
                if (src_type == FileType.DIRECTORY) {
                        if (!dest.query_exists () || !merge_flag) {
-                               dest.make_directory ();
+                               dest.make_directory_with_parents ();
                                src.copy_attributes (dest, FileCopyFlags.NONE);
                        }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]