diff options
author | Garrett Brown <garbearucla@gmail.com> | 2014-03-02 17:04:45 -0800 |
---|---|---|
committer | Garrett Brown <themagnificentmrb@gmail.com> | 2016-12-01 18:08:29 -0800 |
commit | e2563f6529ea0f08fe153d6baa9dff0935ea8d65 (patch) | |
tree | a96d39b1dc64a0da9e13570ed4b869faede717a1 | |
parent | 918e289427c97e9f108b1c9e2219537644ad4955 (diff) |
[retroplayer] Game add-ons
Thanks to Themaister for rewind functionality, fetzerch for mouse support,
file length check and cmake modifications, topfs2 for fixing a crash when
loading game clients, eibma for fixing linux compilation errors, a1rwulf
for catching a missing callback symbol and fixing some rebase errors, and
to notspiff for helping with the rebrand and cmake.
156 files changed, 9954 insertions, 62 deletions
diff --git a/.gitignore b/.gitignore index 44ef5abaa2..3a617d0fa4 100644 --- a/.gitignore +++ b/.gitignore @@ -129,6 +129,7 @@ cmake_install.cmake /addons/pvr.* /addons/adsp.* /addons/peripheral.* +/addons/game.* /addons/xbmc.addon/addon.xml /addons/xbmc.json/addon.xml /addons/kodi.guilib/addon.xml @@ -151,6 +152,7 @@ cmake_install.cmake /lib/addons/library.xbmc.pvr/Makefile /lib/addons/library.xbmc.codec/Makefile /lib/addons/library.kodi.peripheral/Makefile +/lib/addons/library.kodi.game/Makefile /lib/addons/library.xbmc.addon/project/VS2010Express/Release /lib/addons/library.xbmc.addon/project/VS2010Express/Debug /lib/addons/library.kodi.adsp/project/VS2010Express/Release @@ -165,6 +167,8 @@ cmake_install.cmake /lib/addons/library.kodi.inputstream/Makefile /lib/addons/library.kodi.peripheral/project/VS2010Express/Release /lib/addons/library.kodi.peripheral/project/VS2010Express/Debug +/lib/addons/library.kodi.game/project/VS2010Express/Release +/lib/addons/library.kodi.game/project/VS2010Express/Debug # /lib/cpluff/ /lib/cpluff/ABOUT-NLS diff --git a/Kodi.xcodeproj/project.pbxproj b/Kodi.xcodeproj/project.pbxproj index 203bcdc719..6ad12ab571 100644 --- a/Kodi.xcodeproj/project.pbxproj +++ b/Kodi.xcodeproj/project.pbxproj @@ -261,6 +261,62 @@ 6838CF821D6665510057F17B /* DeadzoneFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6838CF7F1D6665510057F17B /* DeadzoneFilter.cpp */; }; 6861B9EA1CC248EE00F62655 /* DriverReceiving.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6861B9E81CC248EE00F62655 /* DriverReceiving.cpp */; }; 6861B9EB1CC248EE00F62655 /* DriverReceiving.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6861B9E81CC248EE00F62655 /* DriverReceiving.cpp */; }; + 6890C1DA1DDBDBE500F8F362 /* BasicMemoryStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1C61DDBDBE500F8F362 /* BasicMemoryStream.cpp */; }; + 6890C1DB1DDBDBE500F8F362 /* BasicMemoryStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1C61DDBDBE500F8F362 /* BasicMemoryStream.cpp */; }; + 6890C1DC1DDBDBE500F8F362 /* DeltaPairMemoryStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1C81DDBDBE500F8F362 /* DeltaPairMemoryStream.cpp */; }; + 6890C1DD1DDBDBE500F8F362 /* DeltaPairMemoryStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1C81DDBDBE500F8F362 /* DeltaPairMemoryStream.cpp */; }; + 6890C1DE1DDBDBE500F8F362 /* LinearMemoryStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1CB1DDBDBE500F8F362 /* LinearMemoryStream.cpp */; }; + 6890C1DF1DDBDBE500F8F362 /* LinearMemoryStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1CB1DDBDBE500F8F362 /* LinearMemoryStream.cpp */; }; + 6890C1E01DDBDBE500F8F362 /* Savestate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1CD1DDBDBE500F8F362 /* Savestate.cpp */; }; + 6890C1E11DDBDBE500F8F362 /* Savestate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1CD1DDBDBE500F8F362 /* Savestate.cpp */; }; + 6890C1E21DDBDBE500F8F362 /* SavestateDatabase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1CF1DDBDBE500F8F362 /* SavestateDatabase.cpp */; }; + 6890C1E31DDBDBE500F8F362 /* SavestateDatabase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1CF1DDBDBE500F8F362 /* SavestateDatabase.cpp */; }; + 6890C1E41DDBDBE500F8F362 /* SavestateReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1D21DDBDBE500F8F362 /* SavestateReader.cpp */; }; + 6890C1E51DDBDBE500F8F362 /* SavestateReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1D21DDBDBE500F8F362 /* SavestateReader.cpp */; }; + 6890C1E61DDBDBE500F8F362 /* SavestateTranslator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1D41DDBDBE500F8F362 /* SavestateTranslator.cpp */; }; + 6890C1E71DDBDBE500F8F362 /* SavestateTranslator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1D41DDBDBE500F8F362 /* SavestateTranslator.cpp */; }; + 6890C1E81DDBDBE500F8F362 /* SavestateUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1D61DDBDBE500F8F362 /* SavestateUtils.cpp */; }; + 6890C1E91DDBDBE500F8F362 /* SavestateUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1D61DDBDBE500F8F362 /* SavestateUtils.cpp */; }; + 6890C1EA1DDBDBE500F8F362 /* SavestateWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1D81DDBDBE500F8F362 /* SavestateWriter.cpp */; }; + 6890C1EB1DDBDBE500F8F362 /* SavestateWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1D81DDBDBE500F8F362 /* SavestateWriter.cpp */; }; + 6890C1F21DDBDBEA00F8F362 /* GameClientReversiblePlayback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1ED1DDBDBEA00F8F362 /* GameClientReversiblePlayback.cpp */; }; + 6890C1F31DDBDBEA00F8F362 /* GameClientReversiblePlayback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1ED1DDBDBEA00F8F362 /* GameClientReversiblePlayback.cpp */; }; + 6890C1F41DDBDBEA00F8F362 /* GameLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1EF1DDBDBEA00F8F362 /* GameLoop.cpp */; }; + 6890C1F51DDBDBEA00F8F362 /* GameLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1EF1DDBDBEA00F8F362 /* GameLoop.cpp */; }; + 6890C2051DDBDBFC00F8F362 /* GameClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1F61DDBDBFC00F8F362 /* GameClient.cpp */; }; + 6890C2061DDBDBFC00F8F362 /* GameClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1F61DDBDBFC00F8F362 /* GameClient.cpp */; }; + 6890C2071DDBDBFC00F8F362 /* GameClientInput.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1F91DDBDBFC00F8F362 /* GameClientInput.cpp */; }; + 6890C2081DDBDBFC00F8F362 /* GameClientInput.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1F91DDBDBFC00F8F362 /* GameClientInput.cpp */; }; + 6890C2091DDBDBFC00F8F362 /* GameClientKeyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1FB1DDBDBFC00F8F362 /* GameClientKeyboard.cpp */; }; + 6890C20A1DDBDBFC00F8F362 /* GameClientKeyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1FB1DDBDBFC00F8F362 /* GameClientKeyboard.cpp */; }; + 6890C20B1DDBDBFC00F8F362 /* GameClientMouse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1FD1DDBDBFC00F8F362 /* GameClientMouse.cpp */; }; + 6890C20C1DDBDBFC00F8F362 /* GameClientMouse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1FD1DDBDBFC00F8F362 /* GameClientMouse.cpp */; }; + 6890C20D1DDBDBFC00F8F362 /* GameClientProperties.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1FF1DDBDBFC00F8F362 /* GameClientProperties.cpp */; }; + 6890C20E1DDBDBFC00F8F362 /* GameClientProperties.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C1FF1DDBDBFC00F8F362 /* GameClientProperties.cpp */; }; + 6890C20F1DDBDBFC00F8F362 /* GameClientTiming.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C2011DDBDBFC00F8F362 /* GameClientTiming.cpp */; }; + 6890C2101DDBDBFC00F8F362 /* GameClientTiming.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C2011DDBDBFC00F8F362 /* GameClientTiming.cpp */; }; + 6890C2111DDBDBFC00F8F362 /* GameClientTranslator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C2031DDBDBFC00F8F362 /* GameClientTranslator.cpp */; }; + 6890C2121DDBDBFC00F8F362 /* GameClientTranslator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C2031DDBDBFC00F8F362 /* GameClientTranslator.cpp */; }; + 6890C21D1DDBDCA200F8F362 /* GameResource.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C21B1DDBDCA200F8F362 /* GameResource.cpp */; }; + 6890C21E1DDBDCA200F8F362 /* GameResource.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C21B1DDBDCA200F8F362 /* GameResource.cpp */; }; + 6890C2271DDBDCF500F8F362 /* AddonCallbacksGame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C2251DDBDCF500F8F362 /* AddonCallbacksGame.cpp */; }; + 6890C2281DDBDCF500F8F362 /* AddonCallbacksGame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C2251DDBDCF500F8F362 /* AddonCallbacksGame.cpp */; }; + 6890C22F1DDBDD2D00F8F362 /* GameUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C22D1DDBDD2D00F8F362 /* GameUtils.cpp */; }; + 6890C2301DDBDD2D00F8F362 /* GameUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C22D1DDBDD2D00F8F362 /* GameUtils.cpp */; }; + 6890C2381DDBDD4400F8F362 /* GUIDialogSelectGameClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C2341DDBDD4400F8F362 /* GUIDialogSelectGameClient.cpp */; }; + 6890C2391DDBDD4400F8F362 /* GUIDialogSelectGameClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C2341DDBDD4400F8F362 /* GUIDialogSelectGameClient.cpp */; }; + 6890C23F1DDBDD6A00F8F362 /* PortManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C23B1DDBDD6A00F8F362 /* PortManager.cpp */; }; + 6890C2401DDBDD6A00F8F362 /* PortManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C23B1DDBDD6A00F8F362 /* PortManager.cpp */; }; + 6890C2411DDBDD6A00F8F362 /* PortMapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C23D1DDBDD6A00F8F362 /* PortMapper.cpp */; }; + 6890C2421DDBDD6A00F8F362 /* PortMapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C23D1DDBDD6A00F8F362 /* PortMapper.cpp */; }; + 6890C2461DDBDD9300F8F362 /* JoystickEasterEgg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C2441DDBDD9200F8F362 /* JoystickEasterEgg.cpp */; }; + 6890C2471DDBDD9300F8F362 /* JoystickEasterEgg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C2441DDBDD9200F8F362 /* JoystickEasterEgg.cpp */; }; + 6890C24A1DDBDDA400F8F362 /* KeyboardEasterEgg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C2481DDBDDA400F8F362 /* KeyboardEasterEgg.cpp */; }; + 6890C24B1DDBDDA400F8F362 /* KeyboardEasterEgg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C2481DDBDDA400F8F362 /* KeyboardEasterEgg.cpp */; }; + 6890C2501DDBDDC900F8F362 /* MouseInputHandling.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C24E1DDBDDC900F8F362 /* MouseInputHandling.cpp */; }; + 6890C2511DDBDDC900F8F362 /* MouseInputHandling.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C24E1DDBDDC900F8F362 /* MouseInputHandling.cpp */; }; + 6890C2571DDBDDD500F8F362 /* MouseWindowingButtonMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C2551DDBDDD500F8F362 /* MouseWindowingButtonMap.cpp */; }; + 6890C2581DDBDDD500F8F362 /* MouseWindowingButtonMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6890C2551DDBDDD500F8F362 /* MouseWindowingButtonMap.cpp */; }; 68AE5BA51C92412900C4D527 /* AddonCallbacksPeripheral.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68AE5BA31C92412900C4D527 /* AddonCallbacksPeripheral.cpp */; }; 68AE5BA61C92412900C4D527 /* AddonCallbacksPeripheral.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68AE5BA31C92412900C4D527 /* AddonCallbacksPeripheral.cpp */; }; 68AE5BBD1C9241DF00C4D527 /* DefaultJoystick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68AE5BAC1C9241DF00C4D527 /* DefaultJoystick.cpp */; }; @@ -325,8 +381,6 @@ 68AE5C341C9243A000C4D527 /* ControllerTranslator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68AE5C2A1C9243A000C4D527 /* ControllerTranslator.cpp */; }; 68B7E5E81D5FA9B300A5AEC0 /* GUIFeatureControls.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68B7E5E61D5FA9B300A5AEC0 /* GUIFeatureControls.cpp */; }; 68B7E5E91D5FA9B300A5AEC0 /* GUIFeatureControls.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68B7E5E61D5FA9B300A5AEC0 /* GUIFeatureControls.cpp */; }; - 68D9167A1DD0430E00058B06 /* GUIDialogNewJoystick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68D916781DD0430E00058B06 /* GUIDialogNewJoystick.cpp */; }; - 68D9167B1DD0430E00058B06 /* GUIDialogNewJoystick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68D916781DD0430E00058B06 /* GUIDialogNewJoystick.cpp */; }; 68D77CFF1CD1C5E4004C1735 /* GameSettings.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68D77CFD1CD1C5E4004C1735 /* GameSettings.cpp */; }; 68D77D001CD1C5E4004C1735 /* GameSettings.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68D77CFD1CD1C5E4004C1735 /* GameSettings.cpp */; }; 68D77D0A1CD1C64C004C1735 /* JoystickEmulation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68D77D061CD1C64C004C1735 /* JoystickEmulation.cpp */; }; @@ -337,6 +391,8 @@ 68D77D131CD1C68A004C1735 /* PeripheralJoystickEmulation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68D77D101CD1C68A004C1735 /* PeripheralJoystickEmulation.cpp */; }; 68D77D171CD1C6D9004C1735 /* GameInfoTag.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68D77D151CD1C6D9004C1735 /* GameInfoTag.cpp */; }; 68D77D181CD1C6D9004C1735 /* GameInfoTag.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68D77D151CD1C6D9004C1735 /* GameInfoTag.cpp */; }; + 68D9167A1DD0430E00058B06 /* GUIDialogNewJoystick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68D916781DD0430E00058B06 /* GUIDialogNewJoystick.cpp */; }; + 68D9167B1DD0430E00058B06 /* GUIDialogNewJoystick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 68D916781DD0430E00058B06 /* GUIDialogNewJoystick.cpp */; }; 761170901C8B85F8006C6366 /* AddonGUIRenderingControl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7611708C1C8B85F8006C6366 /* AddonGUIRenderingControl.cpp */; }; 761170911C8B85F8006C6366 /* AddonGUIWindow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7611708E1C8B85F8006C6366 /* AddonGUIWindow.cpp */; }; 76AEFB361C8F79BD00EF2EC0 /* AddonInterfaces.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDED2E991C878F61000F5E80 /* AddonInterfaces.cpp */; }; @@ -2786,6 +2842,83 @@ 6861B9E91CC248EE00F62655 /* DriverReceiving.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DriverReceiving.h; path = joysticks/generic/DriverReceiving.h; sourceTree = "<group>"; }; 6861B9EC1CC248F600F62655 /* IDriverReceiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IDriverReceiver.h; path = joysticks/IDriverReceiver.h; sourceTree = "<group>"; }; 6861B9ED1CC248F600F62655 /* IInputReceiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IInputReceiver.h; path = joysticks/IInputReceiver.h; sourceTree = "<group>"; }; + 6890C1C61DDBDBE500F8F362 /* BasicMemoryStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BasicMemoryStream.cpp; path = games/addons/savestates/BasicMemoryStream.cpp; sourceTree = "<group>"; }; + 6890C1C71DDBDBE500F8F362 /* BasicMemoryStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BasicMemoryStream.h; path = games/addons/savestates/BasicMemoryStream.h; sourceTree = "<group>"; }; + 6890C1C81DDBDBE500F8F362 /* DeltaPairMemoryStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DeltaPairMemoryStream.cpp; path = games/addons/savestates/DeltaPairMemoryStream.cpp; sourceTree = "<group>"; }; + 6890C1C91DDBDBE500F8F362 /* DeltaPairMemoryStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DeltaPairMemoryStream.h; path = games/addons/savestates/DeltaPairMemoryStream.h; sourceTree = "<group>"; }; + 6890C1CA1DDBDBE500F8F362 /* IMemoryStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IMemoryStream.h; path = games/addons/savestates/IMemoryStream.h; sourceTree = "<group>"; }; + 6890C1CB1DDBDBE500F8F362 /* LinearMemoryStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LinearMemoryStream.cpp; path = games/addons/savestates/LinearMemoryStream.cpp; sourceTree = "<group>"; }; + 6890C1CC1DDBDBE500F8F362 /* LinearMemoryStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LinearMemoryStream.h; path = games/addons/savestates/LinearMemoryStream.h; sourceTree = "<group>"; }; + 6890C1CD1DDBDBE500F8F362 /* Savestate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Savestate.cpp; path = games/addons/savestates/Savestate.cpp; sourceTree = "<group>"; }; + 6890C1CE1DDBDBE500F8F362 /* Savestate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Savestate.h; path = games/addons/savestates/Savestate.h; sourceTree = "<group>"; }; + 6890C1CF1DDBDBE500F8F362 /* SavestateDatabase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SavestateDatabase.cpp; path = games/addons/savestates/SavestateDatabase.cpp; sourceTree = "<group>"; }; + 6890C1D01DDBDBE500F8F362 /* SavestateDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SavestateDatabase.h; path = games/addons/savestates/SavestateDatabase.h; sourceTree = "<group>"; }; + 6890C1D11DDBDBE500F8F362 /* SavestateDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SavestateDefines.h; path = games/addons/savestates/SavestateDefines.h; sourceTree = "<group>"; }; + 6890C1D21DDBDBE500F8F362 /* SavestateReader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SavestateReader.cpp; path = games/addons/savestates/SavestateReader.cpp; sourceTree = "<group>"; }; + 6890C1D31DDBDBE500F8F362 /* SavestateReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SavestateReader.h; path = games/addons/savestates/SavestateReader.h; sourceTree = "<group>"; }; + 6890C1D41DDBDBE500F8F362 /* SavestateTranslator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SavestateTranslator.cpp; path = games/addons/savestates/SavestateTranslator.cpp; sourceTree = "<group>"; }; + 6890C1D51DDBDBE500F8F362 /* SavestateTranslator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SavestateTranslator.h; path = games/addons/savestates/SavestateTranslator.h; sourceTree = "<group>"; }; + 6890C1D61DDBDBE500F8F362 /* SavestateUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SavestateUtils.cpp; path = games/addons/savestates/SavestateUtils.cpp; sourceTree = "<group>"; }; + 6890C1D71DDBDBE500F8F362 /* SavestateUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SavestateUtils.h; path = games/addons/savestates/SavestateUtils.h; sourceTree = "<group>"; }; + 6890C1D81DDBDBE500F8F362 /* SavestateWriter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SavestateWriter.cpp; path = games/addons/savestates/SavestateWriter.cpp; sourceTree = "<group>"; }; + 6890C1D91DDBDBE500F8F362 /* SavestateWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SavestateWriter.h; path = games/addons/savestates/SavestateWriter.h; sourceTree = "<group>"; }; + 6890C1EC1DDBDBEA00F8F362 /* GameClientRealtimePlayback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameClientRealtimePlayback.h; path = games/addons/playback/GameClientRealtimePlayback.h; sourceTree = "<group>"; }; + 6890C1ED1DDBDBEA00F8F362 /* GameClientReversiblePlayback.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GameClientReversiblePlayback.cpp; path = games/addons/playback/GameClientReversiblePlayback.cpp; sourceTree = "<group>"; }; + 6890C1EE1DDBDBEA00F8F362 /* GameClientReversiblePlayback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameClientReversiblePlayback.h; path = games/addons/playback/GameClientReversiblePlayback.h; sourceTree = "<group>"; }; + 6890C1EF1DDBDBEA00F8F362 /* GameLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GameLoop.cpp; path = games/addons/playback/GameLoop.cpp; sourceTree = "<group>"; }; + 6890C1F01DDBDBEA00F8F362 /* GameLoop.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameLoop.h; path = games/addons/playback/GameLoop.h; sourceTree = "<group>"; }; + 6890C1F11DDBDBEA00F8F362 /* IGameClientPlayback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IGameClientPlayback.h; path = games/addons/playback/IGameClientPlayback.h; sourceTree = "<group>"; }; + 6890C1F61DDBDBFC00F8F362 /* GameClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GameClient.cpp; path = games/addons/GameClient.cpp; sourceTree = "<group>"; }; + 6890C1F71DDBDBFC00F8F362 /* GameClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameClient.h; path = games/addons/GameClient.h; sourceTree = "<group>"; }; + 6890C1F81DDBDBFC00F8F362 /* GameClientCallbacks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameClientCallbacks.h; path = games/addons/GameClientCallbacks.h; sourceTree = "<group>"; }; + 6890C1F91DDBDBFC00F8F362 /* GameClientInput.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GameClientInput.cpp; path = games/addons/GameClientInput.cpp; sourceTree = "<group>"; }; + 6890C1FA1DDBDBFC00F8F362 /* GameClientInput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameClientInput.h; path = games/addons/GameClientInput.h; sourceTree = "<group>"; }; + 6890C1FB1DDBDBFC00F8F362 /* GameClientKeyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GameClientKeyboard.cpp; path = games/addons/GameClientKeyboard.cpp; sourceTree = "<group>"; }; + 6890C1FC1DDBDBFC00F8F362 /* GameClientKeyboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameClientKeyboard.h; path = games/addons/GameClientKeyboard.h; sourceTree = "<group>"; }; + 6890C1FD1DDBDBFC00F8F362 /* GameClientMouse.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GameClientMouse.cpp; path = games/addons/GameClientMouse.cpp; sourceTree = "<group>"; }; + 6890C1FE1DDBDBFC00F8F362 /* GameClientMouse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameClientMouse.h; path = games/addons/GameClientMouse.h; sourceTree = "<group>"; }; + 6890C1FF1DDBDBFC00F8F362 /* GameClientProperties.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GameClientProperties.cpp; path = games/addons/GameClientProperties.cpp; sourceTree = "<group>"; }; + 6890C2001DDBDBFC00F8F362 /* GameClientProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameClientProperties.h; path = games/addons/GameClientProperties.h; sourceTree = "<group>"; }; + 6890C2011DDBDBFC00F8F362 /* GameClientTiming.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GameClientTiming.cpp; path = games/addons/GameClientTiming.cpp; sourceTree = "<group>"; }; + 6890C2021DDBDBFC00F8F362 /* GameClientTiming.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameClientTiming.h; path = games/addons/GameClientTiming.h; sourceTree = "<group>"; }; + 6890C2031DDBDBFC00F8F362 /* GameClientTranslator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GameClientTranslator.cpp; path = games/addons/GameClientTranslator.cpp; sourceTree = "<group>"; }; + 6890C2041DDBDBFC00F8F362 /* GameClientTranslator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameClientTranslator.h; path = games/addons/GameClientTranslator.h; sourceTree = "<group>"; }; + 6890C2181DDBDC5300F8F362 /* IArchivable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IArchivable.h; sourceTree = "<group>"; }; + 6890C2191DDBDC6E00F8F362 /* ISerializable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ISerializable.h; sourceTree = "<group>"; }; + 6890C21A1DDBDC7400F8F362 /* IXmlDeserializable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IXmlDeserializable.h; sourceTree = "<group>"; }; + 6890C21B1DDBDCA200F8F362 /* GameResource.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GameResource.cpp; sourceTree = "<group>"; }; + 6890C21C1DDBDCA200F8F362 /* GameResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GameResource.h; sourceTree = "<group>"; }; + 6890C21F1DDBDCB000F8F362 /* DllGameClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DllGameClient.h; sourceTree = "<group>"; }; + 6890C2201DDBDCB700F8F362 /* DllAudioDSP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DllAudioDSP.h; sourceTree = "<group>"; }; + 6890C2211DDBDCBC00F8F362 /* DllLibCPluff.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DllLibCPluff.h; sourceTree = "<group>"; }; + 6890C2221DDBDCC900F8F362 /* DllPeripheral.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DllPeripheral.h; sourceTree = "<group>"; }; + 6890C2231DDBDCCE00F8F362 /* DllPVRClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DllPVRClient.h; sourceTree = "<group>"; }; + 6890C2251DDBDCF500F8F362 /* AddonCallbacksGame.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AddonCallbacksGame.cpp; path = addons/binary/interfaces/api1/Game/AddonCallbacksGame.cpp; sourceTree = "<group>"; }; + 6890C2261DDBDCF500F8F362 /* AddonCallbacksGame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AddonCallbacksGame.h; path = addons/binary/interfaces/api1/Game/AddonCallbacksGame.h; sourceTree = "<group>"; }; + 6890C2291DDBDD0900F8F362 /* kodi_game_callbacks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = kodi_game_callbacks.h; path = "kodi-addon-dev-kit/include/kodi/kodi_game_callbacks.h"; sourceTree = "<group>"; }; + 6890C22A1DDBDD0900F8F362 /* kodi_game_dll.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = kodi_game_dll.h; path = "kodi-addon-dev-kit/include/kodi/kodi_game_dll.h"; sourceTree = "<group>"; }; + 6890C22B1DDBDD0900F8F362 /* kodi_game_types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = kodi_game_types.h; path = "kodi-addon-dev-kit/include/kodi/kodi_game_types.h"; sourceTree = "<group>"; }; + 6890C22C1DDBDD2D00F8F362 /* GameTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameTypes.h; path = games/GameTypes.h; sourceTree = "<group>"; }; + 6890C22D1DDBDD2D00F8F362 /* GameUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GameUtils.cpp; path = games/GameUtils.cpp; sourceTree = "<group>"; }; + 6890C22E1DDBDD2D00F8F362 /* GameUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameUtils.h; path = games/GameUtils.h; sourceTree = "<group>"; }; + 6890C2341DDBDD4400F8F362 /* GUIDialogSelectGameClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GUIDialogSelectGameClient.cpp; path = games/dialogs/GUIDialogSelectGameClient.cpp; sourceTree = "<group>"; }; + 6890C2351DDBDD4400F8F362 /* GUIDialogSelectGameClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GUIDialogSelectGameClient.h; path = games/dialogs/GUIDialogSelectGameClient.h; sourceTree = "<group>"; }; + 6890C23B1DDBDD6A00F8F362 /* PortManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PortManager.cpp; path = games/ports/PortManager.cpp; sourceTree = "<group>"; }; + 6890C23C1DDBDD6A00F8F362 /* PortManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PortManager.h; path = games/ports/PortManager.h; sourceTree = "<group>"; }; + 6890C23D1DDBDD6A00F8F362 /* PortMapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PortMapper.cpp; path = games/ports/PortMapper.cpp; sourceTree = "<group>"; }; + 6890C23E1DDBDD6A00F8F362 /* PortMapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PortMapper.h; path = games/ports/PortMapper.h; sourceTree = "<group>"; }; + 6890C2431DDBDD8900F8F362 /* IButtonSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IButtonSequence.h; path = joysticks/IButtonSequence.h; sourceTree = "<group>"; }; + 6890C2441DDBDD9200F8F362 /* JoystickEasterEgg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = JoystickEasterEgg.cpp; path = joysticks/JoystickEasterEgg.cpp; sourceTree = "<group>"; }; + 6890C2451DDBDD9200F8F362 /* JoystickEasterEgg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JoystickEasterEgg.h; path = joysticks/JoystickEasterEgg.h; sourceTree = "<group>"; }; + 6890C2481DDBDDA400F8F362 /* KeyboardEasterEgg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = KeyboardEasterEgg.cpp; path = keyboard/KeyboardEasterEgg.cpp; sourceTree = "<group>"; }; + 6890C2491DDBDDA400F8F362 /* KeyboardEasterEgg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = KeyboardEasterEgg.h; path = keyboard/KeyboardEasterEgg.h; sourceTree = "<group>"; }; + 6890C24E1DDBDDC900F8F362 /* MouseInputHandling.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MouseInputHandling.cpp; path = mouse/generic/MouseInputHandling.cpp; sourceTree = "<group>"; }; + 6890C24F1DDBDDC900F8F362 /* MouseInputHandling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MouseInputHandling.h; path = mouse/generic/MouseInputHandling.h; sourceTree = "<group>"; }; + 6890C2521DDBDDD500F8F362 /* IMouseButtonMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IMouseButtonMap.h; path = mouse/IMouseButtonMap.h; sourceTree = "<group>"; }; + 6890C2531DDBDDD500F8F362 /* IMouseDriverHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IMouseDriverHandler.h; path = mouse/IMouseDriverHandler.h; sourceTree = "<group>"; }; + 6890C2541DDBDDD500F8F362 /* IMouseInputHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IMouseInputHandler.h; path = mouse/IMouseInputHandler.h; sourceTree = "<group>"; }; + 6890C2551DDBDDD500F8F362 /* MouseWindowingButtonMap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MouseWindowingButtonMap.cpp; path = mouse/MouseWindowingButtonMap.cpp; sourceTree = "<group>"; }; + 6890C2561DDBDDD500F8F362 /* MouseWindowingButtonMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MouseWindowingButtonMap.h; path = mouse/MouseWindowingButtonMap.h; sourceTree = "<group>"; }; 68AE5BA01C923E5300C4D527 /* kodi_vfs_utils.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = kodi_vfs_utils.hpp; path = "kodi-addon-dev-kit/include/kodi/kodi_vfs_utils.hpp"; sourceTree = "<group>"; }; 68AE5BA31C92412900C4D527 /* AddonCallbacksPeripheral.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AddonCallbacksPeripheral.cpp; path = addons/binary/interfaces/api1/Peripheral/AddonCallbacksPeripheral.cpp; sourceTree = "<group>"; }; 68AE5BA41C92412900C4D527 /* AddonCallbacksPeripheral.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AddonCallbacksPeripheral.h; path = addons/binary/interfaces/api1/Peripheral/AddonCallbacksPeripheral.h; sourceTree = "<group>"; }; @@ -2866,8 +2999,6 @@ 68AE5C2C1C9243A000C4D527 /* ControllerTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ControllerTypes.h; path = games/controllers/ControllerTypes.h; sourceTree = "<group>"; }; 68B7E5E61D5FA9B300A5AEC0 /* GUIFeatureControls.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GUIFeatureControls.cpp; path = games/controllers/guicontrols/GUIFeatureControls.cpp; sourceTree = "<group>"; }; 68B7E5E71D5FA9B300A5AEC0 /* GUIFeatureControls.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GUIFeatureControls.h; path = games/controllers/guicontrols/GUIFeatureControls.h; sourceTree = "<group>"; }; - 68D916781DD0430E00058B06 /* GUIDialogNewJoystick.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GUIDialogNewJoystick.cpp; path = joysticks/dialogs/GUIDialogNewJoystick.cpp; sourceTree = "<group>"; }; - 68D916791DD0430E00058B06 /* GUIDialogNewJoystick.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GUIDialogNewJoystick.h; path = joysticks/dialogs/GUIDialogNewJoystick.h; sourceTree = "<group>"; }; 68D77CFD1CD1C5E4004C1735 /* GameSettings.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GameSettings.cpp; path = games/GameSettings.cpp; sourceTree = "<group>"; }; 68D77CFE1CD1C5E4004C1735 /* GameSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameSettings.h; path = games/GameSettings.h; sourceTree = "<group>"; }; 68D77D021CD1C638004C1735 /* IKeyboardHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IKeyboardHandler.h; path = keyboard/IKeyboardHandler.h; sourceTree = "<group>"; }; @@ -2879,6 +3010,8 @@ 68D77D111CD1C68A004C1735 /* PeripheralJoystickEmulation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PeripheralJoystickEmulation.h; sourceTree = "<group>"; }; 68D77D151CD1C6D9004C1735 /* GameInfoTag.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GameInfoTag.cpp; path = games/tags/GameInfoTag.cpp; sourceTree = "<group>"; }; 68D77D161CD1C6D9004C1735 /* GameInfoTag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameInfoTag.h; path = games/tags/GameInfoTag.h; sourceTree = "<group>"; }; + 68D916781DD0430E00058B06 /* GUIDialogNewJoystick.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GUIDialogNewJoystick.cpp; path = joysticks/dialogs/GUIDialogNewJoystick.cpp; sourceTree = "<group>"; }; + 68D916791DD0430E00058B06 /* GUIDialogNewJoystick.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GUIDialogNewJoystick.h; path = joysticks/dialogs/GUIDialogNewJoystick.h; sourceTree = "<group>"; }; 6E97BDBF0DA2B620003A2A89 /* EventClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EventClient.h; sourceTree = "<group>"; }; 6E97BDC00DA2B620003A2A89 /* EventPacket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EventPacket.h; sourceTree = "<group>"; }; 6E97BDC10DA2B620003A2A89 /* EventServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EventServer.h; sourceTree = "<group>"; }; @@ -5214,8 +5347,15 @@ 9A2FAD681C972BE10049652A /* ContextMenus.cpp */, 9A2FAD691C972BE10049652A /* ContextMenus.h */, 18B49FF61152BFA5001AF8A6 /* DllAddon.h */, + 6890C2201DDBDCB700F8F362 /* DllAudioDSP.h */, + 6890C21F1DDBDCB000F8F362 /* DllGameClient.h */, + 6890C2211DDBDCBC00F8F362 /* DllLibCPluff.h */, + 6890C2221DDBDCC900F8F362 /* DllPeripheral.h */, + 6890C2231DDBDCCE00F8F362 /* DllPVRClient.h */, DF209C101DB2A0C200515D1F /* FilesystemInstaller.cpp */, DF209C111DB2A0C300515D1F /* FilesystemInstaller.h */, + 6890C21B1DDBDCA200F8F362 /* GameResource.cpp */, + 6890C21C1DDBDCA200F8F362 /* GameResource.h */, 18B7C38612942090009E7A26 /* GUIDialogAddonInfo.cpp */, 18B7C38712942090009E7A26 /* GUIDialogAddonInfo.h */, 18B7C8F912942718009E7A26 /* GUIDialogAddonSettings.cpp */, @@ -5505,6 +5645,7 @@ children = ( 68AE5BAB1C92419500C4D527 /* joysticks */, 68D77D011CD1C627004C1735 /* keyboard */, + 6890C24C1DDBDDAF00F8F362 /* mouse */, E4991332174E5E5C00741B6D /* touch */, 18B7C8CB12942546009E7A26 /* ButtonTranslator.cpp */, 18B7C8CC12942546009E7A26 /* ButtonTranslator.h */, @@ -6187,15 +6328,121 @@ name = dialogs; sourceTree = "<group>"; }; - 6827D71B1DD03E5300BE9114 /* dialogs */ = { + 6890C1C31DDBDBB400F8F362 /* addons */ = { isa = PBXGroup; children = ( - 6827D71C1DD03E5F00BE9114 /* GUIDialogNewJoystick.cpp */, - 6827D71D1DD03E5F00BE9114 /* GUIDialogNewJoystick.h */, + 6890C1C51DDBDBCE00F8F362 /* playback */, + 6890C1C41DDBDBC800F8F362 /* savestates */, + 6890C1F61DDBDBFC00F8F362 /* GameClient.cpp */, + 6890C1F71DDBDBFC00F8F362 /* GameClient.h */, + 6890C1F81DDBDBFC00F8F362 /* GameClientCallbacks.h */, + 6890C1F91DDBDBFC00F8F362 /* GameClientInput.cpp */, + 6890C1FA1DDBDBFC00F8F362 /* GameClientInput.h */, + 6890C1FB1DDBDBFC00F8F362 /* GameClientKeyboard.cpp */, + 6890C1FC1DDBDBFC00F8F362 /* GameClientKeyboard.h */, + 6890C1FD1DDBDBFC00F8F362 /* GameClientMouse.cpp */, + 6890C1FE1DDBDBFC00F8F362 /* GameClientMouse.h */, + 6890C1FF1DDBDBFC00F8F362 /* GameClientProperties.cpp */, + 6890C2001DDBDBFC00F8F362 /* GameClientProperties.h */, + 6890C2011DDBDBFC00F8F362 /* GameClientTiming.cpp */, + 6890C2021DDBDBFC00F8F362 /* GameClientTiming.h */, + 6890C2031DDBDBFC00F8F362 /* GameClientTranslator.cpp */, + 6890C2041DDBDBFC00F8F362 /* GameClientTranslator.h */, + ); + name = addons; + sourceTree = "<group>"; + }; + 6890C1C41DDBDBC800F8F362 /* savestates */ = { + isa = PBXGroup; + children = ( + 6890C1C61DDBDBE500F8F362 /* BasicMemoryStream.cpp */, + 6890C1C71DDBDBE500F8F362 /* BasicMemoryStream.h */, + 6890C1C81DDBDBE500F8F362 /* DeltaPairMemoryStream.cpp */, + 6890C1C91DDBDBE500F8F362 /* DeltaPairMemoryStream.h */, + 6890C1CA1DDBDBE500F8F362 /* IMemoryStream.h */, + 6890C1CB1DDBDBE500F8F362 /* LinearMemoryStream.cpp */, + 6890C1CC1DDBDBE500F8F362 /* LinearMemoryStream.h */, + 6890C1CD1DDBDBE500F8F362 /* Savestate.cpp */, + 6890C1CE1DDBDBE500F8F362 /* Savestate.h */, + 6890C1CF1DDBDBE500F8F362 /* SavestateDatabase.cpp */, + 6890C1D01DDBDBE500F8F362 /* SavestateDatabase.h */, + 6890C1D11DDBDBE500F8F362 /* SavestateDefines.h */, + 6890C1D21DDBDBE500F8F362 /* SavestateReader.cpp */, + 6890C1D31DDBDBE500F8F362 /* SavestateReader.h */, + 6890C1D41DDBDBE500F8F362 /* SavestateTranslator.cpp */, + 6890C1D51DDBDBE500F8F362 /* SavestateTranslator.h */, + 6890C1D61DDBDBE500F8F362 /* SavestateUtils.cpp */, + 6890C1D71DDBDBE500F8F362 /* SavestateUtils.h */, + 6890C1D81DDBDBE500F8F362 /* SavestateWriter.cpp */, + 6890C1D91DDBDBE500F8F362 /* SavestateWriter.h */, + ); + name = savestates; + sourceTree = "<group>"; + }; + 6890C1C51DDBDBCE00F8F362 /* playback */ = { + isa = PBXGroup; + children = ( + 6890C1EC1DDBDBEA00F8F362 /* GameClientRealtimePlayback.h */, + 6890C1ED1DDBDBEA00F8F362 /* GameClientReversiblePlayback.cpp */, + 6890C1EE1DDBDBEA00F8F362 /* GameClientReversiblePlayback.h */, + 6890C1EF1DDBDBEA00F8F362 /* GameLoop.cpp */, + 6890C1F01DDBDBEA00F8F362 /* GameLoop.h */, + 6890C1F11DDBDBEA00F8F362 /* IGameClientPlayback.h */, + ); + name = playback; + sourceTree = "<group>"; + }; + 6890C2241DDBDCE300F8F362 /* Game */ = { + isa = PBXGroup; + children = ( + 6890C2251DDBDCF500F8F362 /* AddonCallbacksGame.cpp */, + 6890C2261DDBDCF500F8F362 /* AddonCallbacksGame.h */, + ); + name = Game; + sourceTree = "<group>"; + }; + 6890C2311DDBDD3500F8F362 /* dialogs */ = { + isa = PBXGroup; + children = ( + 6890C2341DDBDD4400F8F362 /* GUIDialogSelectGameClient.cpp */, + 6890C2351DDBDD4400F8F362 /* GUIDialogSelectGameClient.h */, ); name = dialogs; sourceTree = "<group>"; }; + 6890C23A1DDBDD4E00F8F362 /* ports */ = { + isa = PBXGroup; + children = ( + 6890C23B1DDBDD6A00F8F362 /* PortManager.cpp */, + 6890C23C1DDBDD6A00F8F362 /* PortManager.h */, + 6890C23D1DDBDD6A00F8F362 /* PortMapper.cpp */, + 6890C23E1DDBDD6A00F8F362 /* PortMapper.h */, + ); + name = ports; + sourceTree = "<group>"; + }; + 6890C24C1DDBDDAF00F8F362 /* mouse */ = { + isa = PBXGroup; + children = ( + 6890C24D1DDBDDC200F8F362 /* generic */, + 6890C2521DDBDDD500F8F362 /* IMouseButtonMap.h */, + 6890C2531DDBDDD500F8F362 /* IMouseDriverHandler.h */, + 6890C2541DDBDDD500F8F362 /* IMouseInputHandler.h */, + 6890C2551DDBDDD500F8F362 /* MouseWindowingButtonMap.cpp */, + 6890C2561DDBDDD500F8F362 /* MouseWindowingButtonMap.h */, + ); + name = mouse; + sourceTree = "<group>"; + }; + 6890C24D1DDBDDC200F8F362 /* generic */ = { + isa = PBXGroup; + children = ( + 6890C24E1DDBDDC900F8F362 /* MouseInputHandling.cpp */, + 6890C24F1DDBDDC900F8F362 /* MouseInputHandling.h */, + ); + name = generic; + sourceTree = "<group>"; + }; 68AE5BA21C92410300C4D527 /* Peripheral */ = { isa = PBXGroup; children = ( @@ -6220,6 +6467,9 @@ 68AE5BB01C9241DF00C4D527 /* IButtonMap.h */, 6819C8B61D76492500A60E30 /* IButtonMapCallback.h */, 68AE5BB11C9241DF00C4D527 /* IButtonMapper.h */, + 6890C2431DDBDD8900F8F362 /* IButtonSequence.h */, + 6890C2441DDBDD9200F8F362 /* JoystickEasterEgg.cpp */, + 6890C2451DDBDD9200F8F362 /* JoystickEasterEgg.h */, 68AE5BB21C9241DF00C4D527 /* IDriverHandler.h */, 6861B9EC1CC248F600F62655 /* IDriverReceiver.h */, 68AE5BB31C9241DF00C4D527 /* IInputHandler.h */, @@ -6274,10 +6524,16 @@ 68AE5BF91C92431F00C4D527 /* games */ = { isa = PBXGroup; children = ( + 6890C1C31DDBDBB400F8F362 /* addons */, 68AE5BFA1C92433900C4D527 /* controllers */, + 6890C2311DDBDD3500F8F362 /* dialogs */, + 6890C23A1DDBDD4E00F8F362 /* ports */, 68D77D141CD1C6C7004C1735 /* tags */, 68D77CFD1CD1C5E4004C1735 /* GameSettings.cpp */, 68D77CFE1CD1C5E4004C1735 /* GameSettings.h */, + 6890C22C1DDBDD2D00F8F362 /* GameTypes.h */, + 6890C22D1DDBDD2D00F8F362 /* GameUtils.cpp */, + 6890C22E1DDBDD2D00F8F362 /* GameUtils.h */, ); name = games; sourceTree = "<group>"; @@ -6338,37 +6594,13 @@ name = windows; sourceTree = "<group>"; }; - 68D916771DD0430300058B06 /* dialogs */ = { - isa = PBXGroup; - children = ( - 68D916781DD0430E00058B06 /* GUIDialogNewJoystick.cpp */, - 68D916791DD0430E00058B06 /* GUIDialogNewJoystick.h */, - ); - name = dialogs; - sourceTree = "<group>"; - }; - 76EC19BB1D15510D00D4AF19 /* AudioDSPAddons */ = { - isa = PBXGroup; - children = ( - 76EC19BC1D15514200D4AF19 /* ActiveAEDSP.cpp */, - 76EC19BD1D15514200D4AF19 /* ActiveAEDSP.h */, - 76EC19BE1D15514200D4AF19 /* ActiveAEDSPAddon.cpp */, - 76EC19BF1D15514200D4AF19 /* ActiveAEDSPAddon.h */, - 76EC19C01D15514200D4AF19 /* ActiveAEDSPDatabase.cpp */, - 76EC19C11D15514200D4AF19 /* ActiveAEDSPDatabase.h */, - 76EC19C21D15514200D4AF19 /* ActiveAEDSPMode.cpp */, - 76EC19C31D15514200D4AF19 /* ActiveAEDSPMode.h */, - 76EC19C41D15514200D4AF19 /* ActiveAEDSPProcess.cpp */, - 76EC19C51D15514200D4AF19 /* ActiveAEDSPProcess.h */, - ); - name = AudioDSPAddons; - sourceTree = "<group>"; - }; 68D77D011CD1C627004C1735 /* keyboard */ = { isa = PBXGroup; children = ( 68D77D031CD1C63B004C1735 /* generic */, 68D77D021CD1C638004C1735 /* IKeyboardHandler.h */, + 6890C2481DDBDDA400F8F362 /* KeyboardEasterEgg.cpp */, + 6890C2491DDBDDA400F8F362 /* KeyboardEasterEgg.h */, ); name = keyboard; sourceTree = "<group>"; @@ -6391,6 +6623,32 @@ name = tags; sourceTree = "<group>"; }; + 68D916771DD0430300058B06 /* dialogs */ = { + isa = PBXGroup; + children = ( + 68D916781DD0430E00058B06 /* GUIDialogNewJoystick.cpp */, + 68D916791DD0430E00058B06 /* GUIDialogNewJoystick.h */, + ); + name = dialogs; + sourceTree = "<group>"; + }; + 76EC19BB1D15510D00D4AF19 /* AudioDSPAddons */ = { + isa = PBXGroup; + children = ( + 76EC19BC1D15514200D4AF19 /* ActiveAEDSP.cpp */, + 76EC19BD1D15514200D4AF19 /* ActiveAEDSP.h */, + 76EC19BE1D15514200D4AF19 /* ActiveAEDSPAddon.cpp */, + 76EC19BF1D15514200D4AF19 /* ActiveAEDSPAddon.h */, + 76EC19C01D15514200D4AF19 /* ActiveAEDSPDatabase.cpp */, + 76EC19C11D15514200D4AF19 /* ActiveAEDSPDatabase.h */, + 76EC19C21D15514200D4AF19 /* ActiveAEDSPMode.cpp */, + 76EC19C31D15514200D4AF19 /* ActiveAEDSPMode.h */, + 76EC19C41D15514200D4AF19 /* ActiveAEDSPProcess.cpp */, + 76EC19C51D15514200D4AF19 /* ActiveAEDSPProcess.h */, + ); + name = AudioDSPAddons; + sourceTree = "<group>"; + }; 76F4C37B1C8E927A00A1E64B /* InputStream */ = { isa = PBXGroup; children = ( @@ -8934,10 +9192,13 @@ DF56EF231A798A5E00CAAEFB /* HttpRangeUtils.h */, DF52769C151BAEDA00B5B63B /* HttpResponse.cpp */, DF52769D151BAEDA00B5B63B /* HttpResponse.h */, + 6890C2181DDBDC5300F8F362 /* IArchivable.h */, E38E1E4C0D25F9FD00618676 /* InfoLoader.cpp */, E38E1E4D0D25F9FD00618676 /* InfoLoader.h */, DFAF6A4C16EBAE3800D6AE12 /* IRssObserver.h */, + 6890C2191DDBDC6E00F8F362 /* ISerializable.h */, 36A9443E15821E5400727135 /* ISortable.h */, + 6890C21A1DDBDC7400F8F362 /* IXmlDeserializable.h */, 7CAA205B107AFC280096DE39 /* Job.h */, F57B6F7E1071B8B500079ACB /* JobManager.cpp */, F57B6F7F1071B8B500079ACB /* JobManager.h */, @@ -9209,6 +9470,9 @@ EDE8C70F1C7F618500A86ECC /* kodi_audiodec_dll.h */, EDE8C7101C7F618500A86ECC /* kodi_audiodec_types.h */, EDE8C7111C7F618500A86ECC /* kodi_audioengine_types.h */, + 6890C2291DDBDD0900F8F362 /* kodi_game_callbacks.h */, + 6890C22A1DDBDD0900F8F362 /* kodi_game_dll.h */, + 6890C22B1DDBDD0900F8F362 /* kodi_game_types.h */, 7CEE107B1C970BB800E0D426 /* kodi_inputstream_dll.h */, 7CEE107C1C970BB800E0D426 /* kodi_inputstream_types.h */, 68AE5BA71C92414B00C4D527 /* kodi_peripheral_callbacks.h */, @@ -9269,6 +9533,7 @@ EDED2E851C878D3F000F5E80 /* AudioDSP */, EDED2E831C878D04000F5E80 /* AudioEngine */, EDED2E821C878CF6000F5E80 /* Codec */, + 6890C2241DDBDCE300F8F362 /* Game */, EDED2E801C878CB4000F5E80 /* GUI */, 76F4C37B1C8E927A00A1E64B /* InputStream */, 68AE5BA21C92410300C4D527 /* Peripheral */, @@ -9848,6 +10113,7 @@ 7C8E02431BA35D0B0072E8B2 /* SkinBuiltins.cpp in Sources */, E38E1F3C0D25F9FD00618676 /* Autorun.cpp in Sources */, E38E1F3D0D25F9FD00618676 /* AutoSwitch.cpp in Sources */, + 6890C1E81DDBDBE500F8F362 /* SavestateUtils.cpp in Sources */, E38E1F3E0D25F9FD00618676 /* BackgroundInfoLoader.cpp in Sources */, E38E1F450D25F9FD00618676 /* CDDARipper.cpp in Sources */, E38E1F460D25F9FD00618676 /* Encoder.cpp in Sources */, @@ -9882,6 +10148,7 @@ E38E1F890D25F9FD00618676 /* DVDOverlayCodecText.cpp in Sources */, E38E1F8D0D25F9FD00618676 /* DVDVideoCodecFFmpeg.cpp in Sources */, E38E1F8F0D25F9FD00618676 /* DVDVideoPPFFmpeg.cpp in Sources */, + 6890C1E21DDBDBE500F8F362 /* SavestateDatabase.cpp in Sources */, E38E1F910D25F9FD00618676 /* DVDDemux.cpp in Sources */, 7C8E023A1BA35D0B0072E8B2 /* PlayerBuiltins.cpp in Sources */, 80204F121C91CD3600E8C88B /* InputStreamMultiSource.cpp in Sources */, @@ -9906,6 +10173,7 @@ 395C2A141A9F072400EBC7AD /* ResourceFile.cpp in Sources */, E38E1FAA0D25F9FD00618676 /* VideoPlayerVideo.cpp in Sources */, DF91E93E1C0A26350011084D /* SDLMain.mm in Sources */, + 6890C1DC1DDBDBE500F8F362 /* DeltaPairMemoryStream.cpp in Sources */, E38E1FAB0D25F9FD00618676 /* DVDStreamInfo.cpp in Sources */, E38E1FAC0D25F9FD00618676 /* DVDFactorySubtitle.cpp in Sources */, E38E1FAD0D25F9FD00618676 /* DVDSubtitleLineCollection.cpp in Sources */, @@ -9919,6 +10187,8 @@ 68D77D0E1CD1C672004C1735 /* PeripheralBusApplication.cpp in Sources */, E38E1FF00D25F9FD00618676 /* VideoFilterShader.cpp in Sources */, E38E1FF10D25F9FD00618676 /* YUV2RGBShader.cpp in Sources */, + 6890C2051DDBDBFC00F8F362 /* GameClient.cpp in Sources */, + 6890C20D1DDBDBFC00F8F362 /* GameClientProperties.cpp in Sources */, 2AFBB94C1CC608A200BAB340 /* GUIEPGGridContainerModel.cpp in Sources */, E38E1FF70D25F9FD00618676 /* CueDocument.cpp in Sources */, E38E1FF80D25F9FD00618676 /* Database.cpp in Sources */, @@ -9929,6 +10199,7 @@ EDED2E971C878EF8000F5E80 /* AddonCallbacksAudioDSP.cpp in Sources */, E38E1FFC0D25F9FD00618676 /* DynamicDll.cpp in Sources */, E38E1FFF0D25F9FD00618676 /* FileItem.cpp in Sources */, + 6890C1F41DDBDBEA00F8F362 /* GameLoop.cpp in Sources */, E38E20020D25F9FD00618676 /* CacheStrategy.cpp in Sources */, E38E20030D25F9FD00618676 /* CDDADirectory.cpp in Sources */, E38E20040D25F9FD00618676 /* cddb.cpp in Sources */, @@ -9995,6 +10266,7 @@ 68AE5BC31C9241DF00C4D527 /* JoystickTranslator.cpp in Sources */, 7CFC08381C5BA7D0000E5E73 /* DVDDemuxClient.cpp in Sources */, E38E20670D25F9FD00618676 /* DirectoryNodeRecentlyAddedMovies.cpp in Sources */, + 6890C2411DDBDD6A00F8F362 /* PortMapper.cpp in Sources */, E38E20680D25F9FD00618676 /* DirectoryNodeRecentlyAddedMusicVideos.cpp in Sources */, E38E20690D25F9FD00618676 /* DirectoryNodeRoot.cpp in Sources */, E38E206A0D25F9FD00618676 /* DirectoryNodeSeasons.cpp in Sources */, @@ -10008,6 +10280,7 @@ E38E20740D25F9FD00618676 /* VirtualDirectory.cpp in Sources */, E38E20770D25F9FD00618676 /* ZipDirectory.cpp in Sources */, 395C2A041A9CD25100EBC7AD /* ContextItemAddonInvoker.cpp in Sources */, + 6890C1DA1DDBDBE500F8F362 /* BasicMemoryStream.cpp in Sources */, 395C2A091A9F06EB00EBC7AD /* LanguageResource.cpp in Sources */, E38E20780D25F9FD00618676 /* ZipManager.cpp in Sources */, E38E207B0D25F9FD00618676 /* GUIDialogBoxBase.cpp in Sources */, @@ -10102,6 +10375,7 @@ E38E22480D25F9FE00618676 /* isnt.cpp in Sources */, E38E22490D25F9FE00618676 /* log.cpp in Sources */, E38E224B0D25F9FE00618676 /* match.cpp in Sources */, + 6890C2071DDBDBFC00F8F362 /* GameClientInput.cpp in Sources */, E38E224D0D25F9FE00618676 /* options.cpp in Sources */, 395C29BC1A94733100EBC7AD /* Key.cpp in Sources */, E38E224E0D25F9FE00618676 /* pathfn.cpp in Sources */, @@ -10139,6 +10413,7 @@ E38E227E0D25F9FE00618676 /* MusicDatabase.cpp in Sources */, E38E227F0D25F9FE00618676 /* MusicInfoLoader.cpp in Sources */, E38E22800D25F9FE00618676 /* MusicInfoScanner.cpp in Sources */, + 6890C1E01DDBDBE500F8F362 /* Savestate.cpp in Sources */, E38E22970D25F9FE00618676 /* NfoFile.cpp in Sources */, E38E22A00D25F9FE00618676 /* PartyModeManager.cpp in Sources */, B542632B197D353B00726998 /* PosixInterfaceForCLog.cpp in Sources */, @@ -10253,6 +10528,7 @@ E46F7C2D0F77219700C25D29 /* ZeroconfOSX.cpp in Sources */, 83E0B2490F7C95FF0091643F /* Atomics.cpp in Sources */, F5AACA680FB3DE2D00DBB77C /* GUIDialogSelect.cpp in Sources */, + 6890C1E41DDBDBE500F8F362 /* SavestateReader.cpp in Sources */, F5AACA970FB3E2B800DBB77C /* GUIDialogSlider.cpp in Sources */, F59876C00FBA351D008EF4FB /* VideoReferenceClock.cpp in Sources */, F5987F050FBDF274008EF4FB /* DPMSSupport.cpp in Sources */, @@ -10306,14 +10582,17 @@ 7C45DBE910F325C400D4BBF3 /* DAVDirectory.cpp in Sources */, 7CAA57471C8AF6C20032A326 /* DebugRenderer.cpp in Sources */, 7C4B64A71C86F712000E1F74 /* InputStreamAddon.cpp in Sources */, + 6890C1F21DDBDBEA00F8F362 /* GameClientReversiblePlayback.cpp in Sources */, F592568810FBF2E100D2C91D /* ConvolutionKernels.cpp in Sources */, F5DC87E2110A287400EE1B15 /* RingBuffer.cpp in Sources */, F5F244651110DC6B009126C6 /* FileOperationJob.cpp in Sources */, + 6890C24A1DDBDDA400F8F362 /* KeyboardEasterEgg.cpp in Sources */, DF0E4AC51AD597ED00A75430 /* VideoPlayerRadioRDS.cpp in Sources */, F5F245EE1112C9AB009126C6 /* FileUtils.cpp in Sources */, 7C1870621CA1664D00114E45 /* PVRClient.cpp in Sources */, F5A7A702112893E50059D6AA /* AnnouncementManager.cpp in Sources */, F5A7A85B112908F00059D6AA /* WebServer.cpp in Sources */, + 6890C2501DDBDDC900F8F362 /* MouseInputHandling.cpp in Sources */, 7C7B2B301134F36400713D6D /* mysqldataset.cpp in Sources */, F5A7B37E113AFB900059D6AA /* SFTPDirectory.cpp in Sources */, F5A7B42C113CBB950059D6AA /* AddonsDirectory.cpp in Sources */, @@ -10405,6 +10684,7 @@ 18B7C7E01294222E009E7A26 /* GUISpinControlEx.cpp in Sources */, 18B7C7E21294222E009E7A26 /* GUIStaticItem.cpp in Sources */, 18B7C7E31294222E009E7A26 /* GUITextBox.cpp in Sources */, + 6890C20B1DDBDBFC00F8F362 /* GameClientMouse.cpp in Sources */, 18B7C7E41294222E009E7A26 /* GUITextLayout.cpp in Sources */, 18B7C7E51294222E009E7A26 /* GUITexture.cpp in Sources */, 18B7C7E61294222E009E7A26 /* GUITextureD3D.cpp in Sources */, @@ -10451,6 +10731,7 @@ 18B7C933129428CA009E7A26 /* PlayListM3U.cpp in Sources */, 18B7C934129428CA009E7A26 /* PlayListPLS.cpp in Sources */, 18B7C935129428CA009E7A26 /* PlayListURL.cpp in Sources */, + 6890C2091DDBDBFC00F8F362 /* GameClientKeyboard.cpp in Sources */, 18B7C936129428CA009E7A26 /* PlayListWPL.cpp in Sources */, 18B7C937129428CA009E7A26 /* PlayListXML.cpp in Sources */, 18B7C938129428CA009E7A26 /* SmartPlayList.cpp in Sources */, @@ -10510,6 +10791,7 @@ DF98D98C1434F47D00A6EBE1 /* SkinVariable.cpp in Sources */, F5E10537140AA38100175026 /* PeripheralBusUSB.cpp in Sources */, F5E10538140AA38100175026 /* PeripheralBus.cpp in Sources */, + 6890C1EA1DDBDBE500F8F362 /* SavestateWriter.cpp in Sources */, F5E1053B140AA38100175026 /* Peripheral.cpp in Sources */, F5E1053C140AA38100175026 /* PeripheralBluetooth.cpp in Sources */, F5E1053E140AA38100175026 /* PeripheralDisk.cpp in Sources */, @@ -10563,6 +10845,7 @@ DF93D6AB1444A8B1007C6459 /* ShoutcastFile.cpp in Sources */, DF93D6AD1444A8B1007C6459 /* SMBFile.cpp in Sources */, DF93D6AE1444A8B1007C6459 /* SpecialProtocolFile.cpp in Sources */, + 6890C23F1DDBDD6A00F8F362 /* PortManager.cpp in Sources */, DF6A0D811A4584E80075BBFC /* OverrideDirectory.cpp in Sources */, 2AC7EB601C34893700BDAA95 /* PVRRecordingsPath.cpp in Sources */, DF93D6B11444A8B1007C6459 /* UDFFile.cpp in Sources */, @@ -10595,6 +10878,7 @@ C84828C5156CFCD8005A996F /* PVRChannelGroupInternal.cpp in Sources */, C84828C6156CFCD8005A996F /* PVRChannelGroups.cpp in Sources */, C84828C7156CFCD8005A996F /* PVRChannelGroupsContainer.cpp in Sources */, + 6890C21D1DDBDCA200F8F362 /* GameResource.cpp in Sources */, C84828C8156CFCD8005A996F /* GUIDialogPVRChannelManager.cpp in Sources */, C84828C9156CFCD8005A996F /* GUIDialogPVRChannelsOSD.cpp in Sources */, C84828CC156CFCD8005A996F /* GUIDialogPVRGroupManager.cpp in Sources */, @@ -10611,6 +10895,8 @@ C84828D8156CFCD8005A996F /* PVRRecording.cpp in Sources */, 7CED593D1CD341280093F573 /* VTB.cpp in Sources */, C84828D9156CFCD8005A996F /* PVRRecordings.cpp in Sources */, + 6890C2571DDBDDD500F8F362 /* MouseWindowingButtonMap.cpp in Sources */, + 6890C20F1DDBDBFC00F8F362 /* GameClientTiming.cpp in Sources */, C84828DB156CFCD8005A996F /* PVRTimerInfoTag.cpp in Sources */, C84828DC156CFCD8005A996F /* PVRTimers.cpp in Sources */, C84828DD156CFCD8005A996F /* GUIViewStatePVR.cpp in Sources */, @@ -10742,6 +11028,7 @@ DF8990211709BB5400B35C21 /* ViewStateSettings.cpp in Sources */, 395C29D51A98A11C00EBC7AD /* WsgiErrorStream.cpp in Sources */, DF28EDA2170E1A11005FA9D2 /* GUIDialogLockSettings.cpp in Sources */, + 6890C2461DDBDD9300F8F362 /* JoystickEasterEgg.cpp in Sources */, DF28EDA3170E1A11005FA9D2 /* GUIDialogProfileSettings.cpp in Sources */, DF28EDA6170E1A11005FA9D2 /* Profile.cpp in Sources */, DF28EDA7170E1A11005FA9D2 /* ProfilesManager.cpp in Sources */, @@ -10749,12 +11036,14 @@ DF28EE03170E1E51005FA9D2 /* DisplaySettings.cpp in Sources */, 7C87B2CE162CE39600EF897D /* PlayerController.cpp in Sources */, F52CC5F01713AAA200113454 /* DirectoryNodeGrouped.cpp in Sources */, + 6890C2271DDBDCF500F8F362 /* AddonCallbacksGame.cpp in Sources */, F52CC6AA1713BD2B00113454 /* DirectoryNodeGrouped.cpp in Sources */, DFD717271C09EDCE0025D964 /* MessagePrinter.cpp in Sources */, DFECFADF172D9C5100A43CF7 /* GUIControlSettings.cpp in Sources */, DFECFB09172D9CAB00A43CF7 /* SettingAddon.cpp in Sources */, DFECFB0C172D9CAB00A43CF7 /* SettingControl.cpp in Sources */, DFECFB0E172D9CAB00A43CF7 /* SettingPath.cpp in Sources */, + 6890C2111DDBDBFC00F8F362 /* GameClientTranslator.cpp in Sources */, 395F6DDD1A8133360088CC74 /* GUIDialogSimpleMenu.cpp in Sources */, DFECFB1C172D9D0100A43CF7 /* BooleanLogic.cpp in Sources */, DFECFB4C172D9D6D00A43CF7 /* NetworkServices.cpp in Sources */, @@ -10766,6 +11055,7 @@ 9A2FAD6A1C972BE10049652A /* ContextMenus.cpp in Sources */, DFE4095B17417FDF00473BD9 /* LegacyPathTranslation.cpp in Sources */, 39C38CCA1BBFF1EE000F59F5 /* InputCodingTableKorean.cpp in Sources */, + 6890C1E61DDBDBE500F8F362 /* SavestateTranslator.cpp in Sources */, 0E3036EC1760F68A00D93596 /* FavouritesDirectory.cpp in Sources */, 68AE5BE11C92421800C4D527 /* AddonButtonMapping.cpp in Sources */, 68AE5C091C92437900C4D527 /* GUIControllerList.cpp in Sources */, @@ -10796,6 +11086,7 @@ 7C140989183224B8009F9411 /* ISetting.cpp in Sources */, 7CAEF0D21D0E9F0D00B1316C /* GUIDialogCMSSettings.cpp in Sources */, 7C14098C183224B8009F9411 /* ISettingControl.cpp in Sources */, + 6890C2381DDBDD4400F8F362 /* GUIDialogSelectGameClient.cpp in Sources */, 68AE5BDF1C92421800C4D527 /* AddonButtonMap.cpp in Sources */, 7C14098F183224B8009F9411 /* Setting.cpp in Sources */, 7CAEF0CE1D0E9E3800B1316C /* ColorManager.cpp in Sources */, @@ -10862,6 +11153,7 @@ 7CCDA16B192753E30074CF51 /* PltService.cpp in Sources */, 7C2ED53D1C7F7A9800C04032 /* ProcessInfo.cpp in Sources */, 7CCDA174192753E30074CF51 /* PltSsdp.cpp in Sources */, + 6890C1DE1DDBDBE500F8F362 /* LinearMemoryStream.cpp in Sources */, DF0D9F511D63961B006A7DBB /* PlatformDarwinOSX.cpp in Sources */, 7CCDA17D192753E30074CF51 /* PltStateVariable.cpp in Sources */, 7CCDA186192753E30074CF51 /* PltTaskManager.cpp in Sources */, @@ -10960,6 +11252,7 @@ 7CAA469019427AED00008885 /* PosixDirectory.cpp in Sources */, 76EC19CA1D15514200D4AF19 /* ActiveAEDSPProcess.cpp in Sources */, 42DAC16E1A6E789E0066B4C8 /* PVRActionListener.cpp in Sources */, + 6890C22F1DDBDD2D00F8F362 /* GameUtils.cpp in Sources */, DF033D381946612400BFC82E /* AEDeviceEnumerationOSX.cpp in Sources */, 7C525DF5195E2D8100BE3482 /* SaveFileStateJob.cpp in Sources */, 7C908894196358A8003D0619 /* auto_buffer.cpp in Sources */, @@ -10983,6 +11276,7 @@ 7C921F931CD6042500684D0B /* FrameBufferObject.cpp in Sources */, E499114F174E5CC300741B6D /* archive.cpp in Sources */, 68AE5BF01C92424400C4D527 /* PeripheralJoystick.cpp in Sources */, + 6890C1E11DDBDBE500F8F362 /* Savestate.cpp in Sources */, E4991150174E5CC300741B6D /* arcread.cpp in Sources */, E4991151174E5CC300741B6D /* cmddata.cpp in Sources */, E4991152174E5CC300741B6D /* consio.cpp in Sources */, @@ -10991,6 +11285,7 @@ 68AE5BC01C9241DF00C4D527 /* DriverPrimitive.cpp in Sources */, E4991155174E5CC300741B6D /* encname.cpp in Sources */, E4991156174E5CC300741B6D /* errhnd.cpp in Sources */, + 6890C21E1DDBDCA200F8F362 /* GameResource.cpp in Sources */, E4991157174E5CC300741B6D /* extinfo.cpp in Sources */, E4991158174E5CC300741B6D /* extract.cpp in Sources */, 68AE5BCF1C9241F800C4D527 /* ButtonMapping.cpp in Sources */, @@ -11041,6 +11336,7 @@ E4991183174E5CE000741B6D /* GUIDialogAddonInfo.cpp in Sources */, 68AE5C081C92437900C4D527 /* GUIConfigurationWizard.cpp in Sources */, E4991184174E5CE000741B6D /* GUIDialogAddonSettings.cpp in Sources */, + 6890C24B1DDBDDA400F8F362 /* KeyboardEasterEgg.cpp in Sources */, E4991185174E5CE000741B6D /* GUIViewStateAddonBrowser.cpp in Sources */, E4991186174E5CE000741B6D /* GUIWindowAddonBrowser.cpp in Sources */, E4991187174E5CE000741B6D /* PluginSource.cpp in Sources */, @@ -11104,6 +11400,7 @@ E49911D4174E5D2E00741B6D /* DVDDemuxCDDA.cpp in Sources */, E49911D5174E5D2E00741B6D /* DVDDemuxFFmpeg.cpp in Sources */, E49911D9174E5D2E00741B6D /* DVDDemuxUtils.cpp in Sources */, + 6890C2121DDBDBFC00F8F362 /* GameClientTranslator.cpp in Sources */, E49911DA174E5D2E00741B6D /* DVDDemuxVobsub.cpp in Sources */, E49911DB174E5D2E00741B6D /* DVDFactoryDemuxer.cpp in Sources */, E49911DC174E5D3700741B6D /* DVDFactoryInputStream.cpp in Sources */, @@ -11132,6 +11429,7 @@ E49911F3174E5D3E00741B6D /* DVDSubtitleTagMicroDVD.cpp in Sources */, E49911F4174E5D3E00741B6D /* DVDSubtitleTagSami.cpp in Sources */, E49911F5174E5D4500741B6D /* DVDAudio.cpp in Sources */, + 6890C2061DDBDBFC00F8F362 /* GameClient.cpp in Sources */, E49911F6174E5D4500741B6D /* DVDClock.cpp in Sources */, E49911F7174E5D4500741B6D /* DVDDemuxSPU.cpp in Sources */, E49911F8174E5D4500741B6D /* DVDFileInfo.cpp in Sources */, @@ -11140,6 +11438,7 @@ E49911FC174E5D4500741B6D /* DVDOverlayContainer.cpp in Sources */, DF4A3BB31B4B0FC100F9CDC0 /* ApplicationMessenger.cpp in Sources */, DF29BCF21B5D911800904347 /* EventLog.cpp in Sources */, + 6890C2281DDBDCF500F8F362 /* AddonCallbacksGame.cpp in Sources */, E49911FF174E5D4500741B6D /* VideoPlayer.cpp in Sources */, E4991200174E5D4500741B6D /* VideoPlayerAudio.cpp in Sources */, E4991201174E5D4500741B6D /* VideoPlayerSubtitle.cpp in Sources */, @@ -11162,6 +11461,7 @@ E499121E174E5D5A00741B6D /* VideoFilterShader.cpp in Sources */, E499121F174E5D5A00741B6D /* YUV2RGBShader.cpp in Sources */, E4991220174E5D5A00741B6D /* BaseRenderer.cpp in Sources */, + 6890C1DB1DDBDBE500F8F362 /* BasicMemoryStream.cpp in Sources */, E4991222174E5D5A00741B6D /* OverlayRenderer.cpp in Sources */, E4991223174E5D5A00741B6D /* OverlayRendererGL.cpp in Sources */, E4991224174E5D5A00741B6D /* OverlayRendererUtil.cpp in Sources */, @@ -11174,6 +11474,7 @@ E499122B174E5D6100741B6D /* qry_dat.cpp in Sources */, E499122C174E5D6100741B6D /* sqlitedataset.cpp in Sources */, E499122D174E5D6800741B6D /* Epg.cpp in Sources */, + 6890C2101DDBDBFC00F8F362 /* GameClientTiming.cpp in Sources */, B542632C197D353B00726998 /* PosixInterfaceForCLog.cpp in Sources */, E499122E174E5D6800741B6D /* EpgContainer.cpp in Sources */, E499122F174E5D6800741B6D /* EpgDatabase.cpp in Sources */, @@ -11198,6 +11499,7 @@ E499123F174E5D7E00741B6D /* GUIDialogMediaSource.cpp in Sources */, 76AEFB3C1C8F79D100EF2EC0 /* AddonGUIWindow.cpp in Sources */, 395C29E41A98A15700EBC7AD /* HTTPPythonHandler.cpp in Sources */, + 6890C2391DDBDD4400F8F362 /* GUIDialogSelectGameClient.cpp in Sources */, DFE704181D15803F004EAA9D /* ActiveAEDSP.cpp in Sources */, E4991241174E5D7E00741B6D /* GUIDialogNumeric.cpp in Sources */, E4991242174E5D7E00741B6D /* GUIDialogOK.cpp in Sources */, @@ -11205,6 +11507,7 @@ E4991244174E5D7E00741B6D /* GUIDialogPlayerControls.cpp in Sources */, E4991245174E5D7E00741B6D /* GUIDialogProgress.cpp in Sources */, E4991246174E5D7E00741B6D /* GUIDialogSeekBar.cpp in Sources */, + 6890C2471DDBDD9300F8F362 /* JoystickEasterEgg.cpp in Sources */, E4991247174E5D7E00741B6D /* GUIDialogSelect.cpp in Sources */, E4991248174E5D7E00741B6D /* GUIDialogSlider.cpp in Sources */, E4991249174E5D7E00741B6D /* GUIDialogSmartPlaylistEditor.cpp in Sources */, @@ -11386,6 +11689,7 @@ E49912F5174E5DAD00741B6D /* GUIFontTTF.cpp in Sources */, E49912F6174E5DAD00741B6D /* GUIFontTTFDX.cpp in Sources */, E49912F7174E5DAD00741B6D /* GUIFontTTFGL.cpp in Sources */, + 6890C2581DDBDDD500F8F362 /* MouseWindowingButtonMap.cpp in Sources */, E49912F8174E5DAD00741B6D /* GUIImage.cpp in Sources */, E49912F9174E5DAD00741B6D /* GUIIncludes.cpp in Sources */, E49912FA174E5DAD00741B6D /* GUIInfoTypes.cpp in Sources */, @@ -11395,6 +11699,7 @@ E49912FE174E5DAD00741B6D /* GUIListContainer.cpp in Sources */, E49912FF174E5DAD00741B6D /* GUIListGroup.cpp in Sources */, E4991300174E5DAD00741B6D /* GUIListItem.cpp in Sources */, + 6890C2301DDBDD2D00F8F362 /* GameUtils.cpp in Sources */, E4991301174E5DAD00741B6D /* GUIListItemLayout.cpp in Sources */, DFD717611C0A031B0025D964 /* XBMCApplication.m in Sources */, E4991302174E5DAD00741B6D /* GUIListLabel.cpp in Sources */, @@ -11427,6 +11732,7 @@ 395C2A121A9F072400EBC7AD /* ResourceDirectory.cpp in Sources */, E4991318174E5DAD00741B6D /* GUITextureD3D.cpp in Sources */, E4991319174E5DAD00741B6D /* GUITextureGL.cpp in Sources */, + 6890C1E91DDBDBE500F8F362 /* SavestateUtils.cpp in Sources */, E499131A174E5DAD00741B6D /* GUITextureGLES.cpp in Sources */, 2AFBB94D1CC608A200BAB340 /* GUIEPGGridContainerModel.cpp in Sources */, E499131B174E5DAD00741B6D /* GUIToggleButtonControl.cpp in Sources */, @@ -11448,12 +11754,14 @@ E4991328174E5DAD00741B6D /* Texture.cpp in Sources */, E4991329174E5DAD00741B6D /* TextureBundle.cpp in Sources */, 397877D61AAAF87700F98A45 /* Speed.cpp in Sources */, + 6890C1E71DDBDBE500F8F362 /* SavestateTranslator.cpp in Sources */, DF6F52AF1AF6D03F001BC57D /* dacp.cpp in Sources */, E499132A174E5DAD00741B6D /* TextureBundleXBT.cpp in Sources */, E499132C174E5DAD00741B6D /* TextureDX.cpp in Sources */, E499132D174E5DAD00741B6D /* TextureGL.cpp in Sources */, E499132E174E5DAD00741B6D /* TextureManager.cpp in Sources */, E499132F174E5DAD00741B6D /* VisibleEffect.cpp in Sources */, + 6890C1E51DDBDBE500F8F362 /* SavestateReader.cpp in Sources */, E4991330174E5DAD00741B6D /* XBTF.cpp in Sources */, DFC6F4B71AFF7CB10039A7FA /* kiss_fft.c in Sources */, E4991331174E5DAD00741B6D /* XBTFReader.cpp in Sources */, @@ -11483,6 +11791,7 @@ E499135E174E5EEF00741B6D /* AddonModuleXbmc.cpp in Sources */, E499135F174E5EEF00741B6D /* AddonModuleXbmcaddon.cpp in Sources */, 2A7B2BDD1BD6F16600044BCD /* PVRSettings.cpp in Sources */, + 6890C1EB1DDBDBE500F8F362 /* SavestateWriter.cpp in Sources */, E4991360174E5EEF00741B6D /* AddonModuleXbmcgui.cpp in Sources */, E4991361174E5EEF00741B6D /* AddonModuleXbmcplugin.cpp in Sources */, E4991362174E5EEF00741B6D /* AddonModuleXbmcvfs.cpp in Sources */, @@ -11540,6 +11849,7 @@ E49913A1174E5F0E00741B6D /* MusicThumbLoader.cpp in Sources */, E49913A2174E5F0E00741B6D /* Song.cpp in Sources */, E49913A3174E5F2100741B6D /* HTTPImageHandler.cpp in Sources */, + 6890C1F51DDBDBEA00F8F362 /* GameLoop.cpp in Sources */, E49913A4174E5F2100741B6D /* HTTPJsonRpcHandler.cpp in Sources */, E49913A5174E5F2100741B6D /* HTTPVfsHandler.cpp in Sources */, E49913A6174E5F2100741B6D /* HTTPWebinterfaceAddonsHandler.cpp in Sources */, @@ -11581,6 +11891,7 @@ E49913C2174E5F3C00741B6D /* TCPServer.cpp in Sources */, E49913C3174E5F3C00741B6D /* UdpClient.cpp in Sources */, DFEB902919E9337200728978 /* AEResampleFactory.cpp in Sources */, + 6890C1DF1DDBDBE500F8F362 /* LinearMemoryStream.cpp in Sources */, E49913C4174E5F3C00741B6D /* WakeOnAccess.cpp in Sources */, E49913C5174E5F3C00741B6D /* WebServer.cpp in Sources */, E49913C6174E5F3C00741B6D /* Zeroconf.cpp in Sources */, @@ -11625,6 +11936,7 @@ E49913E6174E5F8D00741B6D /* PlayListXML.cpp in Sources */, DFACDB8E1D6CAD06003BBB92 /* Platform.cpp in Sources */, E49913E7174E5F8D00741B6D /* SmartPlayList.cpp in Sources */, + 6890C2511DDBDDC900F8F362 /* MouseInputHandling.cpp in Sources */, E49913E8174E5F9900741B6D /* CocoaPowerSyscall.cpp in Sources */, E49913E9174E5F9900741B6D /* DPMSSupport.cpp in Sources */, E49913EA174E5F9900741B6D /* PowerManager.cpp in Sources */, @@ -11663,7 +11975,9 @@ E4991409174E5FB900741B6D /* GUIWindowPVRChannels.cpp in Sources */, E499140B174E5FB900741B6D /* GUIWindowPVRGuide.cpp in Sources */, E499140C174E5FB900741B6D /* GUIWindowPVRRecordings.cpp in Sources */, + 6890C20A1DDBDBFC00F8F362 /* GameClientKeyboard.cpp in Sources */, E499140D174E5FB900741B6D /* GUIWindowPVRSearch.cpp in Sources */, + 6890C1F31DDBDBEA00F8F362 /* GameClientReversiblePlayback.cpp in Sources */, E499140E174E5FB900741B6D /* GUIWindowPVRTimers.cpp in Sources */, E499140F174E5FB900741B6D /* PVRDatabase.cpp in Sources */, E4991410174E5FB900741B6D /* PVRGUIInfo.cpp in Sources */, @@ -11683,6 +11997,7 @@ E499141E174E603C00741B6D /* AdvancedSettings.cpp in Sources */, E499141F174E603C00741B6D /* DisplaySettings.cpp in Sources */, E4991421174E603C00741B6D /* MediaSettings.cpp in Sources */, + 6890C20C1DDBDBFC00F8F362 /* GameClientMouse.cpp in Sources */, E4991422174E603C00741B6D /* MediaSourceSettings.cpp in Sources */, E4991424174E603C00741B6D /* SettingAddon.cpp in Sources */, 39C38CE21BCD600E000F59F5 /* FFmpegImage.cpp in Sources */, @@ -11829,6 +12144,7 @@ DF91E93B1C0A21D60011084D /* xbmc.cpp in Sources */, E499152C174E640800741B6D /* Application.cpp in Sources */, DFD7172D1C09F5CF0025D964 /* XbmcContext.cpp in Sources */, + 6890C1DD1DDBDBE500F8F362 /* DeltaPairMemoryStream.cpp in Sources */, E499152E174E642900741B6D /* AppParamParser.cpp in Sources */, E499152F174E642900741B6D /* Autorun.cpp in Sources */, E4991530174E642900741B6D /* AutoSwitch.cpp in Sources */, @@ -11882,6 +12198,7 @@ E499155C174E656E00741B6D /* LanguageHook.cpp in Sources */, E499155D174E656E00741B6D /* ListItem.cpp in Sources */, E499155E174E656E00741B6D /* ModuleXbmc.cpp in Sources */, + 6890C2421DDBDD6A00F8F362 /* PortMapper.cpp in Sources */, E499155F174E656E00741B6D /* ModuleXbmcgui.cpp in Sources */, E4991560174E656E00741B6D /* ModuleXbmcplugin.cpp in Sources */, E4991561174E656E00741B6D /* ModuleXbmcvfs.cpp in Sources */, @@ -12003,6 +12320,7 @@ 7CCDA199192753E30074CF51 /* PltUPnP.cpp in Sources */, DF54F7FF1B6580AD000FCBA4 /* ContextMenuItem.cpp in Sources */, 7CCDA1A2192753E30074CF51 /* PltMediaConnect.cpp in Sources */, + 6890C20E1DDBDBFC00F8F362 /* GameClientProperties.cpp in Sources */, 7CCDA1AB192753E30074CF51 /* PltXbox360.cpp in Sources */, 7CCDA1B0192753E30074CF51 /* X_MS_MediaReceiverRegistrarSCPD.cpp in Sources */, 7CCDA1BB192753E30074CF51 /* AVTransportSCPD.cpp in Sources */, @@ -12034,6 +12352,7 @@ 7CCDA7A4192756250074CF51 /* NptCrypto.cpp in Sources */, 7CCDA7A7192756250074CF51 /* NptDataBuffer.cpp in Sources */, 68AE5BA61C92412900C4D527 /* AddonCallbacksPeripheral.cpp in Sources */, + 6890C2081DDBDBFC00F8F362 /* GameClientInput.cpp in Sources */, 7CCDA7B0192756250074CF51 /* NptDebug.cpp in Sources */, 7CCDA7B9192756250074CF51 /* NptDigest.cpp in Sources */, 7CCDA7BC192756250074CF51 /* NptDynamicLibraries.cpp in Sources */, @@ -12048,6 +12367,7 @@ 7CCDA7E6192756250074CF51 /* NptLogging.cpp in Sources */, 7CCDA7E9192756250074CF51 /* NptMessaging.cpp in Sources */, 7CCDA7F2192756250074CF51 /* NptNetwork.cpp in Sources */, + 6890C1E31DDBDBE500F8F362 /* SavestateDatabase.cpp in Sources */, 7CCDA7FB192756250074CF51 /* NptQueue.cpp in Sources */, 3961C43A1ABC0A46002DBBFB /* UISoundsResource.cpp in Sources */, 7CCDA804192756250074CF51 /* NptResults.cpp in Sources */, @@ -12084,6 +12404,7 @@ 7CCDACA819275D1F0074CF51 /* NptStdcDebug.cpp in Sources */, DF29BCFB1B5D911800904347 /* GUIViewStateEventLog.cpp in Sources */, 7CCDACB119275D1F0074CF51 /* NptStdcEnvironment.cpp in Sources */, + 6890C2401DDBDD6A00F8F362 /* PortManager.cpp in Sources */, 7CCDACC219275D790074CF51 /* NptAppleAutoreleasePool.mm in Sources */, 7CCDACCB19275D790074CF51 /* NptAppleLogConfig.mm in Sources */, 7CAA469119427AED00008885 /* PosixDirectory.cpp in Sources */, diff --git a/Makefile.in b/Makefile.in index 3038a9b85d..f28925e9f2 100644 --- a/Makefile.in +++ b/Makefile.in @@ -30,6 +30,7 @@ DIRECTORY_ARCHIVES=$(VideoPlayer_ARCHIVES) \ xbmc/addons/binary/interfaces/api1/AudioDSP/addon-callbacks-audiodsp.a \ xbmc/addons/binary/interfaces/api1/AudioEngine/addon-callbacks-audioengine.a \ xbmc/addons/binary/interfaces/api1/Codec/addon-callbacks-codec.a \ + xbmc/addons/binary/interfaces/api1/Game/addon-callbacks-game.a \ xbmc/addons/binary/interfaces/api1/GUI/addon-callbacks-gui.a \ xbmc/addons/binary/interfaces/api1/InputStream/addon-callbacks-inputstream.a \ xbmc/addons/binary/interfaces/api1/Peripheral/addon-callbacks-peripheral.a \ @@ -53,11 +54,16 @@ DIRECTORY_ARCHIVES=$(VideoPlayer_ARCHIVES) \ xbmc/filesystem/MusicDatabaseDirectory/musicdatabasedirectory.a \ xbmc/filesystem/VideoDatabaseDirectory/videodatabasedirectory.a \ xbmc/filesystem/filesystem.a \ + xbmc/games/addons/gameaddons.a \ + xbmc/games/addons/playback/gameaddonplayback.a \ + xbmc/games/addons/savestates/gamesavestates.a \ xbmc/games/controllers/controllers.a \ xbmc/games/controllers/dialogs/controllerdialogs.a \ xbmc/games/controllers/guicontrols/controllerguicontrols.a \ xbmc/games/controllers/windows/controllerwindows.a \ + xbmc/games/dialogs/gamedialogs.a \ xbmc/games/games.a \ + xbmc/games/ports/gameports.a \ xbmc/games/tags/gameinfotags.a \ xbmc/guilib/guilib.a \ xbmc/input/input.a \ @@ -153,7 +159,10 @@ ifeq ($(findstring osx,@ARCH@),osx) DIRECTORY_ARCHIVES += xbmc/input/joysticks/input_joysticks.a DIRECTORY_ARCHIVES += xbmc/input/joysticks/dialogs/input_joystick_dialogs.a DIRECTORY_ARCHIVES += xbmc/input/joysticks/generic/input_joysticks_generic.a +DIRECTORY_ARCHIVES += xbmc/input/keyboard/input_keyboard.a DIRECTORY_ARCHIVES += xbmc/input/keyboard/generic/input_keyboard_generic.a +DIRECTORY_ARCHIVES += xbmc/input/mouse/input_mouse.a +DIRECTORY_ARCHIVES += xbmc/input/mouse/generic/input_mouse_generic.a DIRECTORY_ARCHIVES += xbmc/network/osx/network.a DIRECTORY_ARCHIVES += xbmc/network/linux/network_linux.a DIRECTORY_ARCHIVES += xbmc/powermanagement/osx/powermanagement.a @@ -170,8 +179,11 @@ ifeq (@USE_ANDROID@,1) DIRECTORY_ARCHIVES += xbmc/input/joysticks/input_joysticks.a DIRECTORY_ARCHIVES += xbmc/input/joysticks/dialogs/input_joystick_dialogs.a DIRECTORY_ARCHIVES += xbmc/input/joysticks/generic/input_joysticks_generic.a +DIRECTORY_ARCHIVES += xbmc/input/keyboard/input_keyboard.a DIRECTORY_ARCHIVES += xbmc/input/keyboard/generic/input_keyboard_generic.a DIRECTORY_ARCHIVES += xbmc/input/linux/input_linux.a +DIRECTORY_ARCHIVES += xbmc/input/mouse/input_mouse.a +DIRECTORY_ARCHIVES += xbmc/input/mouse/generic/input_mouse_generic.a DIRECTORY_ARCHIVES += xbmc/input/touch/input_touch.a DIRECTORY_ARCHIVES += xbmc/input/touch/generic/input_touch_generic.a DIRECTORY_ARCHIVES += xbmc/network/linux/network_linux.a @@ -182,8 +194,11 @@ else DIRECTORY_ARCHIVES += xbmc/input/joysticks/input_joysticks.a DIRECTORY_ARCHIVES += xbmc/input/joysticks/dialogs/input_joystick_dialogs.a DIRECTORY_ARCHIVES += xbmc/input/joysticks/generic/input_joysticks_generic.a +DIRECTORY_ARCHIVES += xbmc/input/keyboard/input_keyboard.a DIRECTORY_ARCHIVES += xbmc/input/keyboard/generic/input_keyboard_generic.a DIRECTORY_ARCHIVES += xbmc/input/linux/input_linux.a +DIRECTORY_ARCHIVES += xbmc/input/mouse/input_mouse.a +DIRECTORY_ARCHIVES += xbmc/input/mouse/generic/input_mouse_generic.a DIRECTORY_ARCHIVES += xbmc/input/touch/input_touch.a DIRECTORY_ARCHIVES += xbmc/input/touch/generic/input_touch_generic.a DIRECTORY_ARCHIVES += xbmc/network/linux/network_linux.a @@ -227,6 +242,7 @@ LIBADDON_DIRS=\ lib/addons/library.kodi.guilib \ lib/addons/library.kodi.inputstream \ lib/addons/library.kodi.peripheral \ + lib/addons/library.kodi.game \ ESTUARY_MEDIA=addons/skin.estuary/media SKIN_DIRS=$(ESTUARY_MEDIA) @@ -347,6 +363,7 @@ libaddon: exports $(MAKE) -C lib/addons/library.kodi.adsp $(MAKE) -C lib/addons/library.kodi.audioengine $(MAKE) -C lib/addons/library.xbmc.codec + $(MAKE) -C lib/addons/library.kodi.game $(MAKE) -C lib/addons/library.kodi.guilib $(MAKE) -C lib/addons/library.kodi.peripheral $(MAKE) -C lib/addons/library.xbmc.pvr diff --git a/addons/kodi.game/addon.xml b/addons/kodi.game/addon.xml index 44f2ca5bb5..e50d0395dc 100644 --- a/addons/kodi.game/addon.xml +++ b/addons/kodi.game/addon.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> -<addon id="kodi.game" version="1.0.14" provider-name="Team-Kodi"> - <backwards-compatibility abi="1.0.14"/> +<addon id="kodi.game" version="1.0.28" provider-name="Team-Kodi"> + <backwards-compatibility abi="1.0.28"/> <requires> <import addon="xbmc.core" version="0.1.0"/> </requires> diff --git a/addons/kodi.resource/games.xsd b/addons/kodi.resource/games.xsd new file mode 100644 index 0000000000..eb9809b51c --- /dev/null +++ b/addons/kodi.resource/games.xsd @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE schema PUBLIC "-//W3C//DTD XMLSCHEMA 200102//EN" "http://www.w3.org/2001/XMLSchema.dtd"> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:element name="extension"> + <xs:complexType> + <xs:attribute name="point" type="xs:string" use="required"/> + </xs:complexType> + </xs:element> +</xs:schema> diff --git a/addons/library.kodi.game/.gitignore b/addons/library.kodi.game/.gitignore new file mode 100644 index 0000000000..76bedaeabb --- /dev/null +++ b/addons/library.kodi.game/.gitignore @@ -0,0 +1,5 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore + diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 1018dd5a5d..427f88e605 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -16070,7 +16070,7 @@ msgstr "" #empty strings from id 35019 to 35048 #. Name of game add-ons category -#: xbmc/filesystem/AddonsDirectory.cpp +#: xbmc/addons/Addon.cpp msgctxt "#35049" msgid "Game add-ons" msgstr "" @@ -16267,6 +16267,16 @@ msgctxt "#35102" msgid "Disable joystick when this device is present" msgstr "" +#. Label for mouse buttons. Used in the controller mapping dialog. +msgctxt "#35103" +msgid "Buttons" +msgstr "" + +#. Label for relative mounters like the mouse pointer. Used in the controller mapping dialog. +msgctxt "#35104" +msgid "Pointers" +msgstr "" + #empty strings from id 35103 to 35149 #. Name of keyboard category in the settings category window @@ -16275,7 +16285,7 @@ msgctxt "#35150" msgid "Keyboard" msgstr "" -#. Name of group for configuring keyboard players +#. Name of group in "Games -> Keyboard" for configuring keyboard players #: system/settings/settings.xml msgctxt "#35151" msgid "Player configuration" @@ -16372,7 +16382,135 @@ msgctxt "#35200" msgid "ALL YOUR BASE ARE BELONG[CR]TO US" msgstr "" -#empty strings from id 35201 to 35504 +#. Name of group in "Games -> General" for configuring gameplay +#: system/settings/settings.xml +msgctxt "#35201" +msgid "Gameplay" +msgstr "" + +#empty string with id 35202 + +#: system/settings/settings.xml +msgctxt "#35203" +msgid "Enable rewind if supported" +msgstr "" + +#: system/settings/settings.xml +msgctxt "#35204" +msgid "Enable real-time rewinding during game play, if supported. Press rewind or manually seek backwards using the seek bar." +msgstr "" + +#: system/settings/settings.xml +msgctxt "#35205" +msgid "Maximum rewind time" +msgstr "" + +#: system/settings/settings.xml +msgctxt "#35206" +msgid "Maximum time possible to rewind, if supported. Large rewind histories can use a lot of RAM." +msgstr "" + +#. Description of add-on category for emulators +#: xbmc/filesystem/AddonsDirectory.cpp +msgctxt "#35207" +msgid "Emulators" +msgstr "" + +#. Description of add-on category for standalone games +#: xbmc/filesystem/AddonsDirectory.cpp +msgctxt "#35208" +msgid "Standalone games" +msgstr "" + +#: xbmc/addons/Addon.cpp +msgctxt "#35209" +msgid "Game resources" +msgstr "" + +#. Dialog title when gameplay fails +#: xbmc/games/addons/GameClient.cpp +#: xbmc/games/GameManager.cpp +msgctxt "#35210" +msgid "Failed to play game" +msgstr "" + +#. Error dialog text when the game requires restricted files to play. %s - emulator ID +#: xbmc/games/addons/GameClient.cpp +msgctxt "#35211" +msgid "This game requires the following add-on: %s" +msgstr "" + +#. Error dialog text when launching a game and no emulators are compatible +#: xbmc/games/GameUtils.cpp +msgctxt "#35212" +msgid "This game isn't compatible with any available emulators." +msgstr "" + +#. Error dialog text when launching a game and the emulator has an internal error. %s - emulator name +#: xbmc/games/GameClient.cpp +msgctxt "#35213" +msgid "The emulator \"%s\" had an internal error." +msgstr "" + +#. Error dialog text when the game can't be played because it's not on a hard drive or partition +#: xbmc/games/GameUtils.cpp +msgctxt "#35214" +msgid "This game can only be played directly from a hard drive or partition. Compressed files must be extracted." +msgstr "" + +#. Error dialog text when libretro support is disabled +#: xbmc/games/addons/GameClientProperties.cpp +msgctxt "#35215" +msgid "This game depends on a disabled add-on. Would you like to enable it?" +msgstr "" + +#. Description of add-on category for game support add-ons like game.libretro (the libretro wrapper) +#: xbmc/filesystem/AddonsDirectory.cpp +msgctxt "#35216" +msgid "Support add-ons" +msgstr "" + +#empty strings from id 35217 to 35219 + +#. Description of add-on category for game providers +#: xbmc/filesystem/AddonsDirectory.cpp +msgctxt "#35220" +msgid "Game providers" +msgstr "" + +#empty strings from id 35221 to 35249 + +#: xbmc/windows/GUIMediaWindow.cpp +msgctxt "#35250" +msgid "Add games..." +msgstr "" + +#: xbmc/dialogs/GUIDialogMediaSource.cpp +msgctxt "#35251" +msgid "Add game source" +msgstr "" + +#: xbmc/dialogs/GUIDialogMediaSource.cpp +msgctxt "#35252" +msgid "Edit game source" +msgstr "" + +#: xbmc/games/windows/GUIWindowGames.cpp +msgctxt "#35253" +msgid "Install emulator" +msgstr "" + +#: xbmc/games/windows/GUIWindowGames.cpp +msgctxt "#35254" +msgid "Manage emulators" +msgstr "" + +#: xbmc/games/dialogs/GUIDialogSelectGameClient.cpp +msgctxt "#35255" +msgid "Browse all emulators" +msgstr "" + +#empty strings from id 35256 to 35504 #. connection state "host unreachable" #: xbmc/addons/AddonCallbacksPVR.cpp diff --git a/configure.ac b/configure.ac index 87bfbbfe8b..1da41576dd 100644 --- a/configure.ac +++ b/configure.ac @@ -2194,6 +2194,7 @@ OUTPUT_FILES="Makefile \ lib/addons/library.kodi.adsp/Makefile \ lib/addons/library.kodi.audioengine/Makefile \ lib/addons/library.xbmc.codec/Makefile \ + lib/addons/library.kodi.game/Makefile \ lib/addons/library.kodi.guilib/Makefile \ lib/addons/library.kodi.peripheral/Makefile \ lib/addons/library.xbmc.pvr/Makefile \ diff --git a/doxygen_resources/pages/mainpage.dox b/doxygen_resources/pages/mainpage.dox index 1c42e87cf2..5017adfe18 100644 --- a/doxygen_resources/pages/mainpage.dox +++ b/doxygen_resources/pages/mainpage.dox @@ -148,6 +148,18 @@ \defgroup mouse Mouse \ingroup input Everything around mouse + + Mouse input is processed by \ref CInputManager and forwarded to + registered mouse handlers (e.g. game clients) or as actions to the UI: + + - If no mouse handlers are registered or if they don't consume events, + the mouse input events are forwarded to the UI via \ref CInputManager::ProcessMouse. + - Clients (e.g. game clients implementing \ref MOUSE::IMouseInputHandler) call + \ref CInputManager::RegisterMouseHandler to register themselves as eligible + for mouse input events. + - Mouse events (from \ref CInputManager::OnEvent) are collected via implementations of + \ref MOUSE::IMouseDriverHandler and transformed into higher level features by + \ref MOUSE::IMouseButtonMap instances before they are sent to the handlers. */ /*! @@ -169,6 +181,11 @@ */ /*! + \defgroup games + Everything about RetroPlayer. +*/ + +/*! \defgroup interface Interfaces Everything around interfaces */ diff --git a/lib/addons/library.kodi.game/CMakeLists.txt b/lib/addons/library.kodi.game/CMakeLists.txt new file mode 100644 index 0000000000..05fd1d0201 --- /dev/null +++ b/lib/addons/library.kodi.game/CMakeLists.txt @@ -0,0 +1,2 @@ +project(KODI_game) +core_add_addon_library(${PROJECT_NAME}) diff --git a/lib/addons/library.kodi.game/Makefile.in b/lib/addons/library.kodi.game/Makefile.in new file mode 100644 index 0000000000..3644126f19 --- /dev/null +++ b/lib/addons/library.kodi.game/Makefile.in @@ -0,0 +1,29 @@ +ARCH=@ARCH@ +INCLUDES=-I. -I../../../xbmc/addons/include +DEFINES+= +CXXFLAGS=-fPIC +LIBNAME=libKODI_game +OBJS=$(LIBNAME).o + +ifeq ($(findstring osx,$(ARCH)), osx) +LIB_SHARED=../../../addons/library.kodi.game/$(LIBNAME)-$(ARCH).dylib +else +LIB_SHARED=../../../addons/library.kodi.game/$(LIBNAME)-$(ARCH).so +endif + +all: $(LIB_SHARED) + +$(LIB_SHARED): $(OBJS) +ifeq ($(findstring osx,$(ARCH)), osx) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -dynamiclib -o $@ $(OBJS) +else + $(CXX) $(CFLAGS) $(LDFLAGS) -shared -g -o $(LIB_SHARED) $(OBJS) +endif + +CLEAN_FILES = \ + $(LIB_SHARED) \ + +DISTCLEAN_FILES= \ + Makefile \ + +include ../../../Makefile.include diff --git a/lib/addons/library.kodi.game/libKODI_game.cpp b/lib/addons/library.kodi.game/libKODI_game.cpp new file mode 100644 index 0000000000..cbcedc4c89 --- /dev/null +++ b/lib/addons/library.kodi.game/libKODI_game.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2014-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "addons/binary/interfaces/AddonInterfaces.h" +#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h" + +#include <stdio.h> + +#ifdef _WIN32 + #include <windows.h> + #define DLLEXPORT __declspec(dllexport) +#else + #define DLLEXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +DLLEXPORT CB_GameLib* GAME_register_me(AddonCB* frontend) +{ + CB_GameLib* cb = NULL; + if (!frontend) + fprintf(stderr, "ERROR: GAME_register_frontend is called with NULL handle!!!\n"); + else + { + cb = frontend->GameLib_RegisterMe(frontend->addonData); + if (!cb) + fprintf(stderr, "ERROR: GAME_register_frontend can't get callback table from frontend!!!\n"); + } + return cb; +} + +DLLEXPORT void GAME_unregister_me(AddonCB* frontend, CB_GameLib* cb) +{ + if (frontend == NULL || cb == NULL) + return; + return frontend->GameLib_UnRegisterMe(frontend->addonData, cb); +} + +DLLEXPORT void GAME_close_game(AddonCB* frontend, CB_GameLib* cb) +{ + if (frontend == NULL || cb == NULL) + return; + return cb->CloseGame(frontend->addonData); +} + +DLLEXPORT int GAME_open_pixel_stream(AddonCB* frontend, CB_GameLib* cb, GAME_PIXEL_FORMAT format, unsigned int width, unsigned int height, GAME_VIDEO_ROTATION rotation) +{ + if (frontend == NULL || cb == NULL) + return -1; + + return cb->OpenPixelStream(frontend->addonData, format, width, height, rotation); +} + +DLLEXPORT int GAME_open_video_stream(AddonCB* frontend, CB_GameLib* cb, GAME_VIDEO_CODEC codec) +{ + if (frontend == NULL || cb == NULL) + return -1; + + return cb->OpenVideoStream(frontend->addonData, codec); +} + +DLLEXPORT int GAME_open_pcm_stream(AddonCB* frontend, CB_GameLib* cb, GAME_PCM_FORMAT format, const GAME_AUDIO_CHANNEL* channel_map) +{ + if (frontend == NULL || cb == NULL) + return -1; + + return cb->OpenPCMStream(frontend->addonData, format, channel_map); +} + +DLLEXPORT int GAME_open_audio_stream(AddonCB* frontend, CB_GameLib* cb, GAME_AUDIO_CODEC codec, const GAME_AUDIO_CHANNEL* channel_map) +{ + if (frontend == NULL || cb == NULL) + return -1; + + return cb->OpenAudioStream(frontend->addonData, codec, channel_map); +} + +DLLEXPORT void GAME_add_stream_data(AddonCB* frontend, CB_GameLib* cb, GAME_STREAM_TYPE stream, const uint8_t* data, unsigned int size) +{ + if (frontend == NULL || cb == NULL) + return; + + return cb->AddStreamData(frontend->addonData, stream, data, size); +} + +DLLEXPORT void GAME_close_stream(AddonCB* frontend, CB_GameLib* cb, GAME_STREAM_TYPE stream) +{ + if (frontend == NULL || cb == NULL) + return; + + return cb->CloseStream(frontend->addonData, stream); +} + +DLLEXPORT void GAME_enable_hardware_rendering(AddonCB* frontend, CB_GameLib* cb, game_hw_info* hw_info) +{ + if (frontend == NULL || cb == NULL) + return; + return cb->EnableHardwareRendering(frontend->addonData, hw_info); +} + +DLLEXPORT uintptr_t GAME_hw_get_current_framebuffer(AddonCB* frontend, CB_GameLib* cb) +{ + if (frontend == NULL || cb == NULL) + return 0; + return cb->HwGetCurrentFramebuffer(frontend->addonData); +} + +DLLEXPORT game_proc_address_t GAME_hw_get_proc_address(AddonCB* frontend, CB_GameLib* cb, const char* sym) +{ + if (frontend == NULL || cb == NULL) + return NULL; + return cb->HwGetProcAddress(frontend->addonData, sym); +} + +DLLEXPORT void GAME_render_frame(AddonCB* frontend, CB_GameLib* cb) +{ + if (frontend == NULL || cb == NULL) + return; + cb->RenderFrame(frontend->addonData); +} + +DLLEXPORT bool GAME_open_port(AddonCB* frontend, CB_GameLib* cb, unsigned int port) +{ + if (frontend == NULL || cb == NULL) + return false; + return cb->OpenPort(frontend->addonData, port); +} + +DLLEXPORT void GAME_close_port(AddonCB* frontend, CB_GameLib* cb, unsigned int port) +{ + if (frontend == NULL || cb == NULL) + return; + return cb->ClosePort(frontend->addonData, port); +} + +DLLEXPORT bool GAME_input_event(AddonCB* frontend, CB_GameLib* cb, const game_input_event* event) +{ + if (frontend == NULL || cb == NULL) + return false; + return cb->InputEvent(frontend->addonData, event); +} + +#ifdef __cplusplus +} +#endif diff --git a/lib/addons/library.kodi.game/project/VS2010Express/libKODI_game.vcxproj b/lib/addons/library.kodi.game/project/VS2010Express/libKODI_game.vcxproj new file mode 100644 index 0000000000..72cf3e72c2 --- /dev/null +++ b/lib/addons/library.kodi.game/project/VS2010Express/libKODI_game.vcxproj @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{1400C916-B4AD-41D7-ACEE-A853F3B89B38}</ProjectGuid> + <RootNamespace>XBMC_VDR</RootNamespace> + <Keyword>Win32Proj</Keyword> + <ProjectName>libKODI_game</ProjectName> + </PropertyGroup> + <Import Project="$(SolutionDir)\XBMC.core-defaults.props" /> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets"> + <Import Project="$(SolutionDir)\XBMC.defaults.props" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup> + <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">..\..\..\..\..\addons\library.kodi.game\</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Debug\</IntDir> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\..\..\..\addons\library.kodi.game\</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Release\</IntDir> + <IncludePath Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">..\..\..\..\..\addons\library.xbmc.addon\;$(IncludePath)</IncludePath> + <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\..\..\..\addons\library.xbmc.addon\;$(IncludePath)</IncludePath> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <AdditionalIncludeDirectories>..\..\..\..\..\xbmc;..\..\..\..\..\xbmc\addons\include;..\..\..\..\..\xbmc\cores\dvdplayer\DVDDemuxers;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_USRDLL;_WIN32PC;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ExceptionHandling>Sync</ExceptionHandling> + <PrecompiledHeader> + </PrecompiledHeader> + </ClCompile> + <Link> + <OutputFile>..\..\..\..\..\addons\library.kodi.game\$(ProjectName).dll</OutputFile> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <AdditionalIncludeDirectories>..\..\..\..\..\xbmc;..\..\..\..\..\xbmc\addons\include;..\..\..\..\..\xbmc\cores\dvdplayer\DVDDemuxers;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_USRDLL;XBMC__WIN32PC;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ExceptionHandling>Sync</ExceptionHandling> + <PrecompiledHeader> + </PrecompiledHeader> + </ClCompile> + <Link> + <OutputFile>..\..\..\..\..\addons\library.kodi.game\$(ProjectName).dll</OutputFile> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="..\..\libKODI_game.cpp" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/lib/addons/library.kodi.game/project/VS2010Express/libKODI_game.vcxproj.filters b/lib/addons/library.kodi.game/project/VS2010Express/libKODI_game.vcxproj.filters new file mode 100644 index 0000000000..3609c12382 --- /dev/null +++ b/lib/addons/library.kodi.game/project/VS2010Express/libKODI_game.vcxproj.filters @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\libKODI_game.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/project/Win32BuildSetup/genNsisIncludes.bat b/project/Win32BuildSetup/genNsisIncludes.bat index 2e481e61a2..e05b04ea98 100644 --- a/project/Win32BuildSetup/genNsisIncludes.bat +++ b/project/Win32BuildSetup/genNsisIncludes.bat @@ -28,6 +28,22 @@ IF EXIST BUILD_WIN32\addons\pvr.* ( ) SET Counter=1 +IF EXIST BUILD_WIN32\addons\game.libretro.* ( + ECHO SectionGroup "Game Add-ons" SecGameAddons >> game-addons.nsi + FOR /F "tokens=*" %%P IN ('dir /B /AD BUILD_WIN32\addons\game.libretro.*') DO ( + FOR /f "delims=<" %%N in ('powershell.exe -ExecutionPolicy Unrestricted -command "& {[xml]$a = get-content BUILD_WIN32\addons\%%P\addon.xml;$a.addon.name}"') do ( + ECHO Section "%%N" SecGameAddons!Counter! >> game-addons.nsi + ECHO SectionIn 1 2 >> game-addons.nsi + ECHO SetOutPath "$INSTDIR\addons\%%P" >> game-addons.nsi + ECHO File /r "${app_root}\addons\%%P\*.*" >> game-addons.nsi + ECHO SectionEnd >> game-addons.nsi + SET /A Counter = !Counter! + 1 + ) + ) + ECHO SectionGroupEnd >> game-addons.nsi +) + +SET Counter=1 IF EXIST BUILD_WIN32\addons\audiodecoder.* ( ECHO SectionGroup "Audio Decoder Add-ons" SecAudioDecoderAddons >> audiodecoder-addons.nsi FOR /F "tokens=*" %%P IN ('dir /B /AD BUILD_WIN32\addons\audiodecoder.*') DO ( diff --git a/project/Win32BuildSetup/genNsisInstaller.nsi b/project/Win32BuildSetup/genNsisInstaller.nsi index c6dd8a1f55..a5741774ea 100644 --- a/project/Win32BuildSetup/genNsisInstaller.nsi +++ b/project/Win32BuildSetup/genNsisInstaller.nsi @@ -230,6 +230,7 @@ SectionEnd !include /nonfatal "audiodecoder-addons.nsi" !include /nonfatal "audioencoder-addons.nsi" !include /nonfatal "audiodsp-addons.nsi" +!include /nonfatal "game-addons.nsi" !include /nonfatal "inputstream-addons.nsi" !include /nonfatal "pvr-addons.nsi" !include /nonfatal "screensaver-addons.nsi" diff --git a/project/cmake/installdata/common/addons.txt b/project/cmake/installdata/common/addons.txt index 1cbe4d690a..72ec5f1fbe 100644 --- a/project/cmake/installdata/common/addons.txt +++ b/project/cmake/installdata/common/addons.txt @@ -20,6 +20,7 @@ addons/xbmc.python/* addons/xbmc.webinterface/* addons/library.kodi.adsp/* addons/library.kodi.audioengine/* +addons/library.kodi.game/* addons/library.kodi.guilib/* addons/library.kodi.inputstream/* addons/library.kodi.peripheral/* diff --git a/project/cmake/scripts/linux/Install.cmake b/project/cmake/scripts/linux/Install.cmake index d8df3598d9..e685854198 100644 --- a/project/cmake/scripts/linux/Install.cmake +++ b/project/cmake/scripts/linux/Install.cmake @@ -160,6 +160,7 @@ install(FILES ${CORE_SOURCE_DIR}/xbmc/addons/kodi-addon-dev-kit/include/kodi/kod ${CORE_SOURCE_DIR}/xbmc/addons/kodi-addon-dev-kit/include/kodi/xbmc_codec_types.h ${CORE_SOURCE_DIR}/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxPacket.h ${CORE_SOURCE_DIR}/xbmc/filesystem/IFileTypes.h + ${CORE_SOURCE_DIR}/xbmc/input/XBMC_vkeys.h DESTINATION ${includedir}/${APP_NAME_LC} COMPONENT kodi-addon-dev) diff --git a/project/cmake/treedata/common/addons.txt b/project/cmake/treedata/common/addons.txt index 76410b7ed6..9d76cf375b 100644 --- a/project/cmake/treedata/common/addons.txt +++ b/project/cmake/treedata/common/addons.txt @@ -1,5 +1,6 @@ lib/addons/library.kodi.adsp KODI_adsp lib/addons/library.kodi.audioengine KODI_audioengine +lib/addons/library.kodi.game KODI_game lib/addons/library.kodi.guilib KODI_guilib lib/addons/library.kodi.inputstream KODI_inputstream lib/addons/library.kodi.peripheral KODI_peripheral diff --git a/project/cmake/treedata/common/games.txt b/project/cmake/treedata/common/games.txt index 2217c6debc..4a0e37bdb8 100644 --- a/project/cmake/treedata/common/games.txt +++ b/project/cmake/treedata/common/games.txt @@ -1,6 +1,11 @@ xbmc/games games +xbmc/games/addons games/addons +xbmc/games/addons/playback games/addons/playback +xbmc/games/addons/savestates games/addons/savestates xbmc/games/controllers games/controllers xbmc/games/controllers/dialogs games/controllers/dialogs xbmc/games/controllers/guicontrols games/controllers/guicontrols xbmc/games/controllers/windows games/controllers/windows +xbmc/games/dialogs games/dialogs +xbmc/games/ports games/ports xbmc/games/tags games/tags diff --git a/project/cmake/treedata/common/subdirs.txt b/project/cmake/treedata/common/subdirs.txt index 89c8c0d46f..af916110a4 100644 --- a/project/cmake/treedata/common/subdirs.txt +++ b/project/cmake/treedata/common/subdirs.txt @@ -5,6 +5,7 @@ xbmc/addons/binary/interfaces/api1/Addon api1AddonCallbacks_Addon xbmc/addons/binary/interfaces/api1/AudioDSP api1AddonCallbacks_AudioDSP xbmc/addons/binary/interfaces/api1/AudioEngine api1AddonCallbacks_AudioEngine xbmc/addons/binary/interfaces/api1/Codec api1AddonCallbacks_Codec +xbmc/addons/binary/interfaces/api1/Game api1AddonCallbacks_Game xbmc/addons/binary/interfaces/api1/GUI api1AddonCallbacks_GUI xbmc/addons/binary/interfaces/api1/InputStream api1AddonCallbacks_InputStream xbmc/addons/binary/interfaces/api1/Peripheral api1AddonCallbacks_Peripheral @@ -20,6 +21,8 @@ xbmc/input/joysticks/dialogs input/joysticks/dialogs xbmc/input/joysticks/generic input/joysticks/generic xbmc/input/keyboard input/keyboard xbmc/input/keyboard/generic input/keyboard/generic +xbmc/input/mouse input/mouse +xbmc/input/mouse/generic input/mouse/generic xbmc/listproviders listproviders xbmc/media media xbmc/messaging messaging diff --git a/system/settings/settings.xml b/system/settings/settings.xml index e876fc0420..a9c53839ee 100644 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -1980,6 +1980,37 @@ </category> </section> <section id="games" label="15016" help="35200"> + <category id="gamesgeneral" label="16000"> + <group id="1" label="35201"> + <setting id="gamesgeneral.enable" type="boolean"> + <visible>false</visible> + <level>0</level> + <default>false</default> + <control type="toggle" /> + </setting> + <setting id="gamesgeneral.enablerewind" type="boolean" label="35203" help="35204"> + <level>0</level> + <default>true</default> + <control type="toggle" /> + </setting> + <setting id="gamesgeneral.rewindtime" type="integer" label="35205" help="35206"> + <level>2</level> + <default>60</default> + <constraints> + <minimum>10</minimum> + <step>10</step> + <maximum>600</maximum> + </constraints> + <dependencies> + <dependency type="enable" setting="gamesgeneral.enablerewind">true</dependency> + </dependencies> + <control type="slider" format="integer"> + <popup>true</popup> + <formatlabel>14045</formatlabel> + </control> + </setting> + </group> + </category> <category id="gameskeyboard" label="35150"> <group id="1" label="128"> <setting id="gameskeyboard.enablekeyboard" type="boolean" label="35152" help="35153"> diff --git a/xbmc/Application.cpp b/xbmc/Application.cpp index f3887822cb..23f03ddcbb 100644 --- a/xbmc/Application.cpp +++ b/xbmc/Application.cpp @@ -3065,6 +3065,17 @@ bool CApplication::PlayMedia(const CFileItem& item, const std::string &player, i return g_PVRManager.PlayMedia(item); } + CURL path(item.GetPath()); + if (path.GetProtocol() == "game") + { + AddonPtr addon; + if (CAddonMgr::GetInstance().GetAddon(path.GetHostName(), addon, ADDON_GAMEDLL)) + { + CFileItem addonItem(addon); + return PlayFile(addonItem, player, false) == PLAYBACK_OK; + } + } + //nothing special just play return PlayFile(item, player, false) == PLAYBACK_OK; } diff --git a/xbmc/DatabaseManager.cpp b/xbmc/DatabaseManager.cpp index cfd839f4cf..dd330fb9ff 100644 --- a/xbmc/DatabaseManager.cpp +++ b/xbmc/DatabaseManager.cpp @@ -27,6 +27,7 @@ #include "video/VideoDatabase.h" #include "pvr/PVRDatabase.h" #include "epg/EpgDatabase.h" +#include "games/addons/savestates/SavestateDatabase.h" #include "settings/AdvancedSettings.h" #include "cores/AudioEngine/Engines/ActiveAE/AudioDSPAddons/ActiveAEDSP.h" diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 9f1c9e33e2..b525b0bdff 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -36,6 +36,8 @@ #include "filesystem/MusicDatabaseDirectory.h" #include "filesystem/VideoDatabaseDirectory.h" #include "filesystem/VideoDatabaseDirectory/QueryParams.h" +#include "games/addons/GameClient.h" +#include "games/GameUtils.h" #include "games/tags/GameInfoTag.h" #include "music/tags/MusicInfoTagLoaderFactory.h" #include "CueDocument.h" @@ -811,6 +813,9 @@ bool CFileItem::IsVideo() const return true; } + //! @todo If the file is a zip file, ask the game clients if any support this + // file before assuming it is video. + return URIUtils::HasExtension(m_strPath, g_advancedSettings.m_videoExtensions); } @@ -890,6 +895,9 @@ bool CFileItem::IsAudio() const return true; } + //! @todo If the file is a zip file, ask the game clients if any support this + // file before assuming it is audio + return URIUtils::HasExtension(m_strPath, g_advancedSettings.GetMusicExtensions()); } @@ -907,7 +915,10 @@ bool CFileItem::IsGame() const if (HasPictureInfoTag()) return false; - return false; + if (HasAddonInfo()) + return CGameUtils::IsStandaloneGame(std::const_pointer_cast<ADDON::IAddon>(GetAddonInfo())); + + return CGameUtils::HasGameExtension(m_strPath); } bool CFileItem::IsPicture() const diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index dca216c771..169241b2be 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -42,6 +42,7 @@ #include "pictures/GUIWindowSlideShow.h" #include "pictures/PictureInfoTag.h" #include "music/tags/MusicInfoTag.h" +#include "games/addons/savestates/SavestateDefines.h" #include "guilib/IGUIContainer.h" #include "guilib/GUIWindowManager.h" #include "PlayListPlayer.h" @@ -10024,6 +10025,11 @@ std::string CGUIInfoManager::GetItemLabel(const CFileItem *item, int info, std:: if (item->GetMusicInfoTag()->GetDuration() > 0) duration = StringUtils::SecondsToTimeString(item->GetMusicInfoTag()->GetDuration()); } + else if (item->HasProperty(FILEITEM_PROPERTY_SAVESTATE_DURATION)) + { + long iDuration = static_cast<long>(item->GetProperty(FILEITEM_PROPERTY_SAVESTATE_DURATION).asInteger()); + duration = StringUtils::SecondsToTimeString(iDuration); + } return duration; } case LISTITEM_PLOT: diff --git a/xbmc/addons/Addon.cpp b/xbmc/addons/Addon.cpp index a9ffe7f741..c24396f955 100644 --- a/xbmc/addons/Addon.cpp +++ b/xbmc/addons/Addon.cpp @@ -93,17 +93,20 @@ static const TypeMapping types[] = {"xbmc.webinterface", ADDON_WEB_INTERFACE, 199, "DefaultAddonWebSkin.png" }, {"xbmc.addon.repository", ADDON_REPOSITORY, 24011, "DefaultAddonRepository.png" }, {"xbmc.pvrclient", ADDON_PVRDLL, 24019, "DefaultAddonPVRClient.png" }, + {"kodi.gameclient", ADDON_GAMEDLL, 35049, "DefaultAddonGame.png" }, {"kodi.peripheral", ADDON_PERIPHERALDLL, 35010, "DefaultAddonPeripheral.png" }, {"xbmc.addon.video", ADDON_VIDEO, 1037, "DefaultAddonVideo.png" }, {"xbmc.addon.audio", ADDON_AUDIO, 1038, "DefaultAddonMusic.png" }, {"xbmc.addon.image", ADDON_IMAGE, 1039, "DefaultAddonPicture.png" }, {"xbmc.addon.executable", ADDON_EXECUTABLE, 1043, "DefaultAddonProgram.png" }, + {"kodi.addon.game", ADDON_GAME, 35049, "DefaultAddonGame.png" }, {"xbmc.audioencoder", ADDON_AUDIOENCODER, 200, "DefaultAddonAudioEncoder.png" }, {"kodi.audiodecoder", ADDON_AUDIODECODER, 201, "DefaultAddonAudioDecoder.png" }, {"xbmc.service", ADDON_SERVICE, 24018, "DefaultAddonService.png" }, {"kodi.resource.images", ADDON_RESOURCE_IMAGES, 24035, "DefaultAddonImages.png" }, {"kodi.resource.language", ADDON_RESOURCE_LANGUAGE, 24026, "DefaultAddonLanguage.png" }, {"kodi.resource.uisounds", ADDON_RESOURCE_UISOUNDS, 24006, "DefaultAddonUISounds.png" }, + {"kodi.resource.games", ADDON_RESOURCE_GAMES, 35209, "DefaultAddonGame.png" }, {"kodi.adsp", ADDON_ADSPDLL, 24135, "DefaultAddonAudioDSP.png" }, {"kodi.inputstream", ADDON_INPUTSTREAM, 24048, "DefaultAddonInputstream.png" }, }; diff --git a/xbmc/addons/AddonBuilder.cpp b/xbmc/addons/AddonBuilder.cpp index 835b439585..f7b9f68372 100644 --- a/xbmc/addons/AddonBuilder.cpp +++ b/xbmc/addons/AddonBuilder.cpp @@ -22,6 +22,7 @@ #include "addons/AudioDecoder.h" #include "addons/AudioEncoder.h" #include "addons/ContextMenuAddon.h" +#include "addons/GameResource.h" #include "addons/ImageResource.h" #include "addons/InputStream.h" #include "addons/LanguageResource.h" @@ -35,6 +36,7 @@ #include "addons/Visualisation.h" #include "addons/Webinterface.h" #include "cores/AudioEngine/Engines/ActiveAE/AudioDSPAddons/ActiveAEDSP.h" +#include "games/addons/GameClient.h" #include "games/controllers/Controller.h" #include "peripherals/addons/PeripheralAddon.h" #include "addons/PVRClient.h" @@ -88,7 +90,8 @@ std::shared_ptr<IAddon> CAddonBuilder::Build() type == ADDON_AUDIOENCODER || type == ADDON_AUDIODECODER || type == ADDON_INPUTSTREAM || - type == ADDON_PERIPHERALDLL) + type == ADDON_PERIPHERALDLL || + type == ADDON_GAMEDLL) { std::string value = CAddonMgr::GetInstance().GetPlatformLibraryName(m_extPoint->plugin->extensions->configuration); if (value.empty()) @@ -137,10 +140,14 @@ std::shared_ptr<IAddon> CAddonBuilder::Build() return CInputStream::FromExtension(std::move(m_props), m_extPoint); case ADDON_PERIPHERALDLL: return PERIPHERALS::CPeripheralAddon::FromExtension(std::move(m_props), m_extPoint); + case ADDON_GAMEDLL: + return GAME::CGameClient::FromExtension(std::move(m_props), m_extPoint); case ADDON_SKIN: return CSkinInfo::FromExtension(std::move(m_props), m_extPoint); case ADDON_RESOURCE_IMAGES: return CImageResource::FromExtension(std::move(m_props), m_extPoint); + case ADDON_RESOURCE_GAMES: + return CGameResource::FromExtension(std::move(m_props), m_extPoint); case ADDON_RESOURCE_LANGUAGE: return CLanguageResource::FromExtension(std::move(m_props), m_extPoint); case ADDON_RESOURCE_UISOUNDS: @@ -203,6 +210,8 @@ AddonPtr CAddonBuilder::FromProps(AddonProps addonProps) return AddonPtr(new CAudioDecoder(std::move(addonProps))); case ADDON_RESOURCE_IMAGES: return AddonPtr(new CImageResource(std::move(addonProps))); + case ADDON_RESOURCE_GAMES: + return AddonPtr(new CGameResource(std::move(addonProps))); case ADDON_RESOURCE_LANGUAGE: return AddonPtr(new CLanguageResource(std::move(addonProps))); case ADDON_RESOURCE_UISOUNDS: @@ -217,6 +226,8 @@ AddonPtr CAddonBuilder::FromProps(AddonProps addonProps) return AddonPtr(new PERIPHERALS::CPeripheralAddon(std::move(addonProps), false, false)); //! @todo implement case ADDON_GAME_CONTROLLER: return AddonPtr(new GAME::CController(std::move(addonProps))); + case ADDON_GAMEDLL: + return AddonPtr(new GAME::CGameClient(std::move(addonProps))); default: break; } diff --git a/xbmc/addons/BinaryAddonCache.cpp b/xbmc/addons/BinaryAddonCache.cpp index 196a2eede1..fe64687d3e 100644 --- a/xbmc/addons/BinaryAddonCache.cpp +++ b/xbmc/addons/BinaryAddonCache.cpp @@ -32,7 +32,12 @@ CBinaryAddonCache::~CBinaryAddonCache() void CBinaryAddonCache::Init() { - m_addonsToCache = {ADDON_AUDIODECODER, ADDON_INPUTSTREAM, ADDON_PVRDLL}; + m_addonsToCache = { + ADDON_AUDIODECODER, + ADDON_INPUTSTREAM, + ADDON_PVRDLL, + ADDON_GAMEDLL, + }; CAddonMgr::GetInstance().Events().Subscribe(this, &CBinaryAddonCache::OnEvent); Update(); } diff --git a/xbmc/addons/CMakeLists.txt b/xbmc/addons/CMakeLists.txt index 8ea5f11f49..927d05db77 100644 --- a/xbmc/addons/CMakeLists.txt +++ b/xbmc/addons/CMakeLists.txt @@ -12,6 +12,7 @@ set(SOURCES Addon.cpp ContextMenuAddon.cpp ContextMenus.cpp FilesystemInstaller.cpp + GameResource.cpp GUIDialogAddonInfo.cpp GUIDialogAddonSettings.cpp GUIViewStateAddonBrowser.cpp @@ -51,6 +52,7 @@ set(HEADERS Addon.h DllLibCPluff.h DllPVRClient.h FilesystemInstaller.h + GameResource.h GUIDialogAddonInfo.h GUIDialogAddonSettings.h GUIViewStateAddonBrowser.h diff --git a/xbmc/addons/DllGameClient.h b/xbmc/addons/DllGameClient.h new file mode 100644 index 0000000000..a34a1e1349 --- /dev/null +++ b/xbmc/addons/DllGameClient.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "DllAddon.h" +#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h" + +class DllGameClient : public DllAddon<GameClient, game_client_properties> +{ + // this is populated via macro calls in DllAddon.h +}; + diff --git a/xbmc/addons/GUIDialogAddonInfo.cpp b/xbmc/addons/GUIDialogAddonInfo.cpp index ee49ec4739..c79b183730 100644 --- a/xbmc/addons/GUIDialogAddonInfo.cpp +++ b/xbmc/addons/GUIDialogAddonInfo.cpp @@ -33,6 +33,7 @@ #include "dialogs/GUIDialogOK.h" #include "dialogs/GUIDialogSelect.h" #include "dialogs/GUIDialogYesNo.h" +#include "games/GameUtils.h" #include "GUIUserMessages.h" #include "guilib/GUIWindowManager.h" #include "input/Key.h" @@ -377,7 +378,16 @@ bool CGUIDialogAddonInfo::CanOpen() const bool CGUIDialogAddonInfo::CanRun() const { - return m_localAddon && m_localAddon->Type() == ADDON_SCRIPT; + if (m_localAddon) + { + if (m_localAddon->Type() == ADDON_SCRIPT) + return true; + + if (GAME::CGameUtils::IsStandaloneGame(m_localAddon)) + return true; + } + + return false; } bool CGUIDialogAddonInfo::CanUse() const diff --git a/xbmc/addons/GUIWindowAddonBrowser.cpp b/xbmc/addons/GUIWindowAddonBrowser.cpp index 68cb844cf9..84a34a13a0 100644 --- a/xbmc/addons/GUIWindowAddonBrowser.cpp +++ b/xbmc/addons/GUIWindowAddonBrowser.cpp @@ -403,6 +403,8 @@ int CGUIWindowAddonBrowser::SelectAddonID(const std::vector<ADDON::TYPE> &types, CAddonsDirectory::GetScriptsAndPlugins("image", typeAddons); else if (*type == ADDON_VIDEO) CAddonsDirectory::GetScriptsAndPlugins("video", typeAddons); + else if (*type == ADDON_GAME) + CAddonsDirectory::GetScriptsAndPlugins("game", typeAddons); else CAddonMgr::GetInstance().GetAddons(typeAddons, *type); diff --git a/xbmc/addons/GameResource.cpp b/xbmc/addons/GameResource.cpp new file mode 100644 index 0000000000..ee6be8f70a --- /dev/null +++ b/xbmc/addons/GameResource.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GameResource.h" + +#include <utility> + +using namespace ADDON; + +CGameResource::CGameResource(AddonProps props) : + CResource(std::move(props)) +{ +} + +std::unique_ptr<CGameResource> CGameResource::FromExtension(AddonProps props, const cp_extension_t* ext) +{ + return std::unique_ptr<CGameResource>(new CGameResource(std::move(props))); +} diff --git a/xbmc/addons/GameResource.h b/xbmc/addons/GameResource.h new file mode 100644 index 0000000000..3ccd24248a --- /dev/null +++ b/xbmc/addons/GameResource.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "addons/Resource.h" + +#include <memory> + +namespace ADDON +{ + +class CGameResource : public CResource +{ +public: + CGameResource(AddonProps props); + virtual ~CGameResource() = default; + + static std::unique_ptr<CGameResource> FromExtension(AddonProps props, const cp_extension_t* ext); + + // implementation of CResource + virtual bool IsAllowed(const std::string& file) const override { return true; } +}; + +} diff --git a/xbmc/addons/IAddon.h b/xbmc/addons/IAddon.h index 1b1dfb3f76..7e4f827a13 100644 --- a/xbmc/addons/IAddon.h +++ b/xbmc/addons/IAddon.h @@ -41,6 +41,7 @@ namespace ADDON ADDON_PVRDLL, ADDON_ADSPDLL, ADDON_INPUTSTREAM, + ADDON_GAMEDLL, ADDON_PERIPHERALDLL, ADDON_SCRIPT, ADDON_SCRIPT_WEATHER, @@ -62,10 +63,12 @@ namespace ADDON ADDON_RESOURCE_IMAGES, ADDON_RESOURCE_LANGUAGE, ADDON_RESOURCE_UISOUNDS, + ADDON_RESOURCE_GAMES, ADDON_VIDEO, // virtual addon types ADDON_AUDIO, ADDON_IMAGE, ADDON_EXECUTABLE, + ADDON_GAME, ADDON_SCRAPER_LIBRARY, ADDON_SCRIPT_LIBRARY, ADDON_SCRIPT_MODULE, diff --git a/xbmc/addons/Makefile b/xbmc/addons/Makefile index ea68de3afd..65d30ca2d4 100644 --- a/xbmc/addons/Makefile +++ b/xbmc/addons/Makefile @@ -12,6 +12,7 @@ SRCS=Addon.cpp \ ContextMenus.cpp \ AudioDecoder.cpp \ FilesystemInstaller.cpp \ + GameResource.cpp \ GUIDialogAddonInfo.cpp \ GUIDialogAddonSettings.cpp \ GUIViewStateAddonBrowser.cpp \ diff --git a/xbmc/addons/PluginSource.cpp b/xbmc/addons/PluginSource.cpp index 14ca09e4ce..dd3c9250c7 100644 --- a/xbmc/addons/PluginSource.cpp +++ b/xbmc/addons/PluginSource.cpp @@ -77,6 +77,8 @@ CPluginSource::Content CPluginSource::Translate(const std::string &content) return CPluginSource::EXECUTABLE; else if (content == "video") return CPluginSource::VIDEO; + else if (content == "game") + return CPluginSource::GAME; else return CPluginSource::UNKNOWN; } @@ -89,6 +91,8 @@ TYPE CPluginSource::FullType() const return ADDON_AUDIO; if (Provides(IMAGE)) return ADDON_IMAGE; + if (Provides(GAME)) + return ADDON_GAME; if (Provides(EXECUTABLE)) return ADDON_EXECUTABLE; @@ -100,6 +104,7 @@ bool CPluginSource::IsType(TYPE type) const return ((type == ADDON_VIDEO && Provides(VIDEO)) || (type == ADDON_AUDIO && Provides(AUDIO)) || (type == ADDON_IMAGE && Provides(IMAGE)) + || (type == ADDON_GAME && Provides(GAME)) || (type == ADDON_EXECUTABLE && Provides(EXECUTABLE))); } diff --git a/xbmc/addons/PluginSource.h b/xbmc/addons/PluginSource.h index eafc12be79..bd059fd2fc 100644 --- a/xbmc/addons/PluginSource.h +++ b/xbmc/addons/PluginSource.h @@ -28,7 +28,7 @@ class CPluginSource : public CAddon { public: - enum Content { UNKNOWN, AUDIO, IMAGE, EXECUTABLE, VIDEO }; + enum Content { UNKNOWN, AUDIO, IMAGE, EXECUTABLE, VIDEO, GAME }; static std::unique_ptr<CPluginSource> FromExtension(AddonProps props, const cp_extension_t* ext); diff --git a/xbmc/addons/addon-bindings.mk b/xbmc/addons/addon-bindings.mk index 6bfed360d7..788b50eb0e 100644 --- a/xbmc/addons/addon-bindings.mk +++ b/xbmc/addons/addon-bindings.mk @@ -13,6 +13,9 @@ BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/xbmc_audioenc_types.h BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_audioengine_types.h BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/xbmc_codec_types.h BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/xbmc_epg_types.h +BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_callbacks.h +BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_dll.h +BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_inputstream_dll.h BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_inputstream_types.h BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_peripheral_callbacks.h @@ -30,6 +33,7 @@ BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/xbmc_vis_types.h BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/libXBMC_addon.h BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_audioengine.h BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_adsp.h +BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_game.h BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_guilib.h BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_inputstream.h BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_peripheral.h @@ -38,3 +42,4 @@ BINDINGS+=xbmc/addons/kodi-addon-dev-kit/include/kodi/libXBMC_codec.h BINDINGS+=xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxPacket.h BINDINGS+=xbmc/cores/AudioEngine/Utils/AEChannelData.h BINDINGS+=xbmc/filesystem/IFileTypes.h +BINDINGS+=xbmc/input/XBMC_vkeys.h diff --git a/xbmc/addons/binary/interfaces/AddonInterfaces.cpp b/xbmc/addons/binary/interfaces/AddonInterfaces.cpp index 5d517a4e01..2809cbb0a4 100644 --- a/xbmc/addons/binary/interfaces/AddonInterfaces.cpp +++ b/xbmc/addons/binary/interfaces/AddonInterfaces.cpp @@ -27,6 +27,7 @@ #include "addons/binary/interfaces/api1/AudioDSP/AddonCallbacksAudioDSP.h" #include "addons/binary/interfaces/api1/AudioEngine/AddonCallbacksAudioEngine.h" #include "addons/binary/interfaces/api1/Codec/AddonCallbacksCodec.h" +#include "addons/binary/interfaces/api1/Game/AddonCallbacksGame.h" #include "addons/binary/interfaces/api1/GUI/AddonCallbacksGUI.h" #include "addons/binary/interfaces/api1/GUI/AddonGUIWindow.h" #include "addons/binary/interfaces/api1/InputStream/AddonCallbacksInputStream.h" @@ -51,7 +52,8 @@ CAddonInterfaces::CAddonInterfaces(CAddon* addon) m_helperADSP(nullptr), m_helperCODEC(nullptr), m_helperInputStream(nullptr), - m_helperPeripheral(nullptr) + m_helperPeripheral(nullptr), + m_helperGame(nullptr) { m_callbacks->libBasePath = strdup(CSpecialProtocol::TranslatePath("special://xbmcbinaddons").c_str()); m_callbacks->addonData = this; @@ -72,6 +74,8 @@ CAddonInterfaces::CAddonInterfaces(CAddon* addon) m_callbacks->INPUTSTREAMLib_UnRegisterMe = CAddonInterfaces::INPUTSTREAMLib_UnRegisterMe; m_callbacks->PeripheralLib_RegisterMe = CAddonInterfaces::PeripheralLib_RegisterMe; m_callbacks->PeripheralLib_UnRegisterMe = CAddonInterfaces::PeripheralLib_UnRegisterMe; + m_callbacks->GameLib_RegisterMe = CAddonInterfaces::GameLib_RegisterMe; + m_callbacks->GameLib_UnRegisterMe = CAddonInterfaces::GameLib_UnRegisterMe; } CAddonInterfaces::~CAddonInterfaces() @@ -84,6 +88,7 @@ CAddonInterfaces::~CAddonInterfaces() delete static_cast<V1::KodiAPI::Codec::CAddonCallbacksCodec*>(m_helperCODEC); delete static_cast<V1::KodiAPI::InputStream::CAddonCallbacksInputStream*>(m_helperInputStream); delete static_cast<V1::KodiAPI::Peripheral::CAddonCallbacksPeripheral*>(m_helperPeripheral); + delete static_cast<V1::KodiAPI::Game::CAddonCallbacksGame*>(m_helperGame); free((char*)m_callbacks->libBasePath); delete m_callbacks; @@ -255,6 +260,33 @@ void CAddonInterfaces::CodecLib_UnRegisterMe(void *addonData, void *cbTable) } /*\_____________________________________________________________________________ \*/ +CB_GameLib* CAddonInterfaces::GameLib_RegisterMe(void *addonData) +{ + CAddonInterfaces* addon = static_cast<CAddonInterfaces*>(addonData); + if (addon == nullptr) + { + CLog::Log(LOGERROR, "CAddonInterfaces - %s - called with a null pointer", __FUNCTION__); + return nullptr; + } + + addon->m_helperGame = new V1::KodiAPI::Game::CAddonCallbacksGame(addon->m_addon); + return static_cast<V1::KodiAPI::Game::CAddonCallbacksGame*>(addon->m_helperGame)->GetCallbacks(); +} + +void CAddonInterfaces::GameLib_UnRegisterMe(void *addonData, CB_GameLib *cbTable) +{ + CAddonInterfaces* addon = static_cast<CAddonInterfaces*>(addonData); + if (addon == nullptr) + { + CLog::Log(LOGERROR, "CAddonInterfaces - %s - called with a null pointer", __FUNCTION__); + return; + } + + delete static_cast<V1::KodiAPI::Game::CAddonCallbacksGame*>(addon->m_helperGame); + addon->m_helperGame = nullptr; +} +/*\_____________________________________________________________________________ +\*/ void* CAddonInterfaces::INPUTSTREAMLib_RegisterMe(void *addonData) { CAddonInterfaces* addon = static_cast<CAddonInterfaces*>(addonData); diff --git a/xbmc/addons/binary/interfaces/AddonInterfaces.h b/xbmc/addons/binary/interfaces/AddonInterfaces.h index 05149bc3fc..3c7c56e5a4 100644 --- a/xbmc/addons/binary/interfaces/AddonInterfaces.h +++ b/xbmc/addons/binary/interfaces/AddonInterfaces.h @@ -22,6 +22,7 @@ #include "IAddonInterface.h" #include "addons/kodi-addon-dev-kit/include/kodi/kodi_peripheral_callbacks.h" +#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_callbacks.h" #include <stdint.h> @@ -49,6 +50,8 @@ typedef void* (*KODIINPUTSTREAMLib_RegisterMe)(void *addonData); typedef void (*KODIINPUTSTREAMLib_UnRegisterMe)(void *addonData, void *cbTable); typedef CB_PeripheralLib* (*KODIPeripheralLib_RegisterMe)(void *addonData); typedef void (*KODIPeripheralLib_UnRegisterMe)(void *addonData, CB_PeripheralLib *cbTable); +typedef CB_GameLib* (*KODIGameLib_RegisterMe)(void *addonData); +typedef void (*KODIGameLib_UnRegisterMe)(void *addonData, CB_GameLib *cbTable); typedef struct AddonCB { @@ -70,6 +73,8 @@ typedef struct AddonCB KODIINPUTSTREAMLib_UnRegisterMe INPUTSTREAMLib_UnRegisterMe; KODIPeripheralLib_RegisterMe PeripheralLib_RegisterMe; KODIPeripheralLib_UnRegisterMe PeripheralLib_UnRegisterMe; + KODIGameLib_RegisterMe GameLib_RegisterMe; + KODIGameLib_UnRegisterMe GameLib_UnRegisterMe; } AddonCB; @@ -127,6 +132,11 @@ namespace ADDON static CB_PeripheralLib* PeripheralLib_RegisterMe (void *addonData); static void PeripheralLib_UnRegisterMe (void *addonData, CB_PeripheralLib* cbTable); void* GetHelperPeripheral() { return m_helperPeripheral; } + /*\_________________________________________________________________________ + \*/ + static CB_GameLib* GameLib_RegisterMe (void *addonData); + static void GameLib_UnRegisterMe (void *addonData, CB_GameLib* cbTable); + void* GetHelperGame() { return m_helperGame; } /* * API level independent functions for Kodi */ @@ -144,6 +154,7 @@ namespace ADDON void* m_helperCODEC; void* m_helperInputStream; void* m_helperPeripheral; + void* m_helperGame; }; } /* namespace ADDON */ diff --git a/xbmc/addons/binary/interfaces/api1/Game/AddonCallbacksGame.cpp b/xbmc/addons/binary/interfaces/api1/Game/AddonCallbacksGame.cpp new file mode 100644 index 0000000000..bf21499102 --- /dev/null +++ b/xbmc/addons/binary/interfaces/api1/Game/AddonCallbacksGame.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "AddonCallbacksGame.h" +#include "cores/AudioEngine/Utils/AEChannelInfo.h" +#include "games/addons/GameClient.h" +#include "guilib/WindowIDs.h" +#include "input/Key.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/log.h" + +#include <string> + +using namespace ADDON; +using namespace GAME; + +namespace V1 +{ +namespace KodiAPI +{ + +namespace Game +{ + +CAddonCallbacksGame::CAddonCallbacksGame(CAddon* addon) : + ADDON::IAddonInterface(addon, 1, GAME_API_VERSION), + m_callbacks(new CB_GameLib) +{ + /* write Kodi game specific add-on function addresses to callback table */ + m_callbacks->CloseGame = CloseGame; + m_callbacks->OpenPixelStream = OpenPixelStream; + m_callbacks->OpenVideoStream = OpenVideoStream; + m_callbacks->OpenPCMStream = OpenPCMStream; + m_callbacks->OpenAudioStream = OpenAudioStream; + m_callbacks->AddStreamData = AddStreamData; + m_callbacks->CloseStream = CloseStream; + m_callbacks->EnableHardwareRendering = EnableHardwareRendering; + m_callbacks->HwGetCurrentFramebuffer = HwGetCurrentFramebuffer; + m_callbacks->HwGetProcAddress = HwGetProcAddress; + m_callbacks->RenderFrame = RenderFrame; + m_callbacks->OpenPort = OpenPort; + m_callbacks->ClosePort = ClosePort; + m_callbacks->InputEvent = InputEvent; +} + +CAddonCallbacksGame::~CAddonCallbacksGame() +{ + /* delete the callback table */ + delete m_callbacks; +} + +CGameClient* CAddonCallbacksGame::GetGameClient(void* addonData, const char* strFunction) +{ + CAddonInterfaces* addon = static_cast<CAddonInterfaces*>(addonData); + if (!addon || !addon->GetHelperGame()) + { + CLog::Log(LOGERROR, "GAME - %s - called with a null pointer", strFunction); + return NULL; + } + + return dynamic_cast<CGameClient*>(static_cast<CAddonCallbacksGame*>(addon->GetHelperGame())->m_addon); +} + +void CAddonCallbacksGame::CloseGame(void* addonData) +{ + using namespace KODI::MESSAGING; + + CApplicationMessenger::GetInstance().PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_STOP))); +} + +int CAddonCallbacksGame::OpenPixelStream(void* addonData, GAME_PIXEL_FORMAT format, unsigned int width, unsigned int height, GAME_VIDEO_ROTATION rotation) +{ + CGameClient* gameClient = GetGameClient(addonData, __FUNCTION__); + if (!gameClient) + return -1; + + return gameClient->OpenPixelStream(format, width, height, rotation) ? 0 : -1; +} + +int CAddonCallbacksGame::OpenVideoStream(void* addonData, GAME_VIDEO_CODEC codec) +{ + CGameClient* gameClient = GetGameClient(addonData, __FUNCTION__); + if (!gameClient) + return -1; + + return gameClient->OpenVideoStream(codec) ? 0 : -1; +} + +int CAddonCallbacksGame::OpenPCMStream(void* addonData, GAME_PCM_FORMAT format, const GAME_AUDIO_CHANNEL* channel_map) +{ + CGameClient* gameClient = GetGameClient(addonData, __FUNCTION__); + if (!gameClient) + return -1; + + return gameClient->OpenPCMStream(format, channel_map) ? 0 : -1; +} + +int CAddonCallbacksGame::OpenAudioStream(void* addonData, GAME_AUDIO_CODEC codec, const GAME_AUDIO_CHANNEL* channel_map) +{ + CGameClient* gameClient = GetGameClient(addonData, __FUNCTION__); + if (!gameClient) + return -1; + + return gameClient->OpenAudioStream(codec, channel_map) ? 0 : -1; +} + +void CAddonCallbacksGame::AddStreamData(void* addonData, GAME_STREAM_TYPE stream, const uint8_t* data, unsigned int size) +{ + CGameClient* gameClient = GetGameClient(addonData, __FUNCTION__); + if (!gameClient) + return; + + gameClient->AddStreamData(stream, data, size); +} + +void CAddonCallbacksGame::CloseStream(void* addonData, GAME_STREAM_TYPE stream) +{ + CGameClient* gameClient = GetGameClient(addonData, __FUNCTION__); + if (!gameClient) + return; + + gameClient->CloseStream(stream); +} + +void CAddonCallbacksGame::EnableHardwareRendering(void* addonData, const game_hw_info *hw_info) +{ + CGameClient* gameClient = GetGameClient(addonData, __FUNCTION__); + if (!gameClient) + return; + + //! @todo +} + +uintptr_t CAddonCallbacksGame::HwGetCurrentFramebuffer(void* addonData) +{ + CGameClient* gameClient = GetGameClient(addonData, __FUNCTION__); + if (!gameClient) + return 0; + + //! @todo + return 0; +} + +game_proc_address_t CAddonCallbacksGame::HwGetProcAddress(void* addonData, const char *sym) +{ + CGameClient* gameClient = GetGameClient(addonData, __FUNCTION__); + if (!gameClient) + return nullptr; + + //! @todo + return nullptr; +} + +void CAddonCallbacksGame::RenderFrame(void* addonData) +{ + CGameClient* gameClient = GetGameClient(addonData, __FUNCTION__); + if (!gameClient) + return; + + //! @todo +} + +bool CAddonCallbacksGame::OpenPort(void* addonData, unsigned int port) +{ + CGameClient* gameClient = GetGameClient(addonData, __FUNCTION__); + if (!gameClient) + return false; + + return gameClient->OpenPort(port); +} + +void CAddonCallbacksGame::ClosePort(void* addonData, unsigned int port) +{ + CGameClient* gameClient = GetGameClient(addonData, __FUNCTION__); + if (!gameClient) + return; + + gameClient->ClosePort(port); +} + +bool CAddonCallbacksGame::InputEvent(void* addonData, const game_input_event* event) +{ + CGameClient* gameClient = GetGameClient(addonData, __FUNCTION__); + if (!gameClient) + return false; + + if (event == nullptr) + return false; + + return gameClient->ReceiveInputEvent(*event); +} + +} /* namespace Game */ + +} /* namespace KodiAPI */ +} /* namespace V1 */ diff --git a/xbmc/addons/binary/interfaces/api1/Game/AddonCallbacksGame.h b/xbmc/addons/binary/interfaces/api1/Game/AddonCallbacksGame.h new file mode 100644 index 0000000000..07136afc6b --- /dev/null +++ b/xbmc/addons/binary/interfaces/api1/Game/AddonCallbacksGame.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "addons/binary/interfaces/AddonInterfaces.h" + +namespace GAME { class CGameClient; } + +namespace V1 +{ +namespace KodiAPI +{ + +namespace Game +{ + +/*! + * Callbacks for a game add-on to Kodi + */ +class CAddonCallbacksGame : public ADDON::IAddonInterface +{ +public: + CAddonCallbacksGame(ADDON::CAddon* addon); + ~CAddonCallbacksGame(void); + + /*! + * @return The callback table. + */ + CB_GameLib* GetCallbacks() const { return m_callbacks; } + + static void CloseGame(void* addonData); + static int OpenPixelStream(void* addonData, GAME_PIXEL_FORMAT format, unsigned int width, unsigned int height, GAME_VIDEO_ROTATION rotation); + static int OpenVideoStream(void* addonData, GAME_VIDEO_CODEC codec); + static int OpenPCMStream(void* addonData, GAME_PCM_FORMAT format, const GAME_AUDIO_CHANNEL* channel_map); + static int OpenAudioStream(void* addonData, GAME_AUDIO_CODEC codec, const GAME_AUDIO_CHANNEL* channel_map); + static void AddStreamData(void* addonData, GAME_STREAM_TYPE stream, const uint8_t* data, unsigned int size); + static void CloseStream(void* addonData, GAME_STREAM_TYPE stream); + static void EnableHardwareRendering(void* addonData, const game_hw_info* hw_info); + static uintptr_t HwGetCurrentFramebuffer(void* addonData); + static game_proc_address_t HwGetProcAddress(void* addonData, const char* sym); + static void RenderFrame(void* addonData); + static bool OpenPort(void* addonData, unsigned int port); + static void ClosePort(void* addonData, unsigned int port); + static bool InputEvent(void* addonData, const game_input_event* event); + +private: + static GAME::CGameClient* GetGameClient(void* addonData, const char* strFunction); + + CB_GameLib* m_callbacks; /*!< callback addresses */ +}; + +} /* namespace Game */ + +} /* namespace KoidAPI */ +} /* namespace V1 */ diff --git a/xbmc/addons/binary/interfaces/api1/Game/CMakeLists.txt b/xbmc/addons/binary/interfaces/api1/Game/CMakeLists.txt new file mode 100644 index 0000000000..48e3cfc1f3 --- /dev/null +++ b/xbmc/addons/binary/interfaces/api1/Game/CMakeLists.txt @@ -0,0 +1,9 @@ +set(SOURCES AddonCallbacksGame.cpp) + +set(HEADERS AddonCallbacksGame.h) + +core_add_library(api1AddonCallbacks_Game) + +if(ENABLE_INTERNAL_FFMPEG) + add_dependencies(api1AddonCallbacks_Game ffmpeg) +endif() diff --git a/xbmc/addons/binary/interfaces/api1/Game/Makefile b/xbmc/addons/binary/interfaces/api1/Game/Makefile new file mode 100644 index 0000000000..460425b058 --- /dev/null +++ b/xbmc/addons/binary/interfaces/api1/Game/Makefile @@ -0,0 +1,7 @@ +SRCS=AddonCallbacksGame.cpp \ + +LIB=addon-callbacks-game.a + +include ../../../../../../Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) + diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_callbacks.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_callbacks.h new file mode 100644 index 0000000000..6057f4635f --- /dev/null +++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_callbacks.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2014-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#ifndef KODI_GAME_CALLBACKS_H_ +#define KODI_GAME_CALLBACKS_H_ + +#include "kodi_game_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct CB_GameLib +{ + // --- Game callbacks -------------------------------------------------------- + + /*! + * \brief Requests the frontend to stop the current game + */ + void (*CloseGame)(void* addonData); + + /*! + * \brief Create a video stream for pixel data + * + * \param format The type of pixel data accepted by this stream + * \param width The frame width + * \param height The frame height + * \param rotation The rotation (counter-clockwise) of the video frames + * + * \return 0 on success or -1 if a video stream is already created + */ + int (*OpenPixelStream)(void* addonData, GAME_PIXEL_FORMAT format, unsigned int width, unsigned int height, GAME_VIDEO_ROTATION rotation); + + /*! + * \brief Create a video stream for encoded video data + * + * \param codec The video format accepted by this stream + * + * \return 0 on success or -1 if a video stream is already created + */ + int (*OpenVideoStream)(void* addonData, GAME_VIDEO_CODEC codec); + + /*! + * \brief Create an audio stream for PCM audio data + * + * \param format The type of audio data accepted by this stream + * \param channel_map The channel layout terminated by GAME_CH_NULL + * + * \return 0 on success or -1 if an audio stream is already created + */ + int (*OpenPCMStream)(void* addonData, GAME_PCM_FORMAT format, const GAME_AUDIO_CHANNEL* channel_map); + + /*! + * \brief Create an audio stream for encoded audio data + * + * \param codec The audio format accepted by this stream + * \param channel_map The channel layout terminated by GAME_CH_NULL + * + * \return 0 on success or -1 if an audio stream is already created + */ + int(*OpenAudioStream)(void* addonData, GAME_AUDIO_CODEC codec, const GAME_AUDIO_CHANNEL* channel_map); + + /*! + * \brief Add a data packet to an audio or video stream + * + * \param stream The target stream + * \param data The data packet + * \param size The size of the data + */ + void (*AddStreamData)(void* addonData, GAME_STREAM_TYPE stream, const uint8_t* data, unsigned int size); + + /*! + * \brief Free the specified stream + * + * \param stream The stream to close + */ + void (*CloseStream)(void* addonData, GAME_STREAM_TYPE stream); + + // -- Hardware rendering callbacks ------------------------------------------- + + /*! + * \brief Enable hardware rendering + * + * \param hw_info A struct of properties for the hardware rendering system + */ + void (*EnableHardwareRendering)(void* addonData, const game_hw_info* hw_info); + + /*! + * \brief Get the framebuffer for rendering + * + * \return The framebuffer + */ + uintptr_t (*HwGetCurrentFramebuffer)(void* addonData); + + /*! + * \brief Get a symbol from the hardware context + * + * \param symbol The symbol's name + * + * \return A function pointer for the specified symbol + */ + game_proc_address_t (*HwGetProcAddress)(void* addonData, const char* symbol); + + /*! + * \brief Called when a frame is being rendered + */ + void (*RenderFrame)(void* addonData); + + // --- Input callbacks ------------------------------------------------------- + + /*! + * \brief Begin reporting events for the specified joystick port + * + * \param port The zero-indexed port number + * + * \return true if the port was opened, false otherwise + */ + bool (*OpenPort)(void* addonData, unsigned int port); + + /*! + * \brief End reporting events for the specified port + * + * \param port The port number passed to OpenPort() + */ + void (*ClosePort)(void* addonData, unsigned int port); + + /*! + * \brief Notify the port of an input event + * + * \param event The input event + * + * Input events can arrive for the following sources: + * - GAME_INPUT_EVENT_MOTOR + * + * \return true if the event was handled, false otherwise + */ + bool (*InputEvent)(void* addonData, const game_input_event* event); + +} CB_GameLib; + +#ifdef __cplusplus +} +#endif + +#endif // KODI_GAME_CALLBACKS_H_ diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_dll.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_dll.h new file mode 100644 index 0000000000..247f24ba27 --- /dev/null +++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_dll.h @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2014-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#ifndef KODI_GAME_DLL_H_ +#define KODI_GAME_DLL_H_ + +#include "kodi_game_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// --- Game API operations ----------------------------------------------------- + +/*! + * \brief Return GAME_API_VERSION_STRING + * + * The add-on is backwards compatible with the frontend if this API version is + * is at least the frontend's minimum API version. + * + * \return Must be GAME_API_VERSION_STRING + */ +const char* GetGameAPIVersion(void); + +/*! + * \brief Return GAME_MIN_API_VERSION_STRING + * + * The add-on is forwards compatible with the frontend if this minimum version + * is no more than the frontend's API version. + * + * \return Must be GAME_MIN_API_VERSION_STRING + */ +const char* GetMininumGameAPIVersion(void); + +// --- Game operations --------------------------------------------------------- + +/*! + * \brief Load a game + * + * \param url The URL to load + * + * return the error, or GAME_ERROR_NO_ERROR if the game was loaded + */ +GAME_ERROR LoadGame(const char* url); + +/*! + * \brief Load a game that requires multiple files + * + * \param type The game stype + * \param urls An array of urls + * \param urlCount The number of urls in the array + * + * \return the error, or GAME_ERROR_NO_ERROR if the game was loaded + */ +GAME_ERROR LoadGameSpecial(SPECIAL_GAME_TYPE type, const char** urls, size_t urlCount); + +/*! + * \brief Begin playing without a game file + * + * If the add-on supports standalone mode, it must add the <supports_standalone> + * tag to the extension point in addon.xml: + * + * <supports_no_game>false</supports_no_game> + * + * \return the error, or GAME_ERROR_NO_ERROR if the game add-on was loaded + */ +GAME_ERROR LoadStandalone(void); + +/*! + * \brief Unload the current game + * + * \return the error, or GAME_ERROR_NO_ERROR if the game was unloaded + */ +/*! Unloads a currently loaded game */ +GAME_ERROR UnloadGame(void); + +/*! + * \brief Get information about the loaded game + * + * \param info The info structure to fill + * + * \return the error, or GAME_ERROR_NO_ERROR if info was filled + */ +GAME_ERROR GetGameInfo(game_system_av_info* info); + +/*! + * \brief Get region of the loaded game + * + * \return the region, or GAME_REGION_UNKNOWN if unknown or no game is loaded + */ +GAME_REGION GetRegion(void); + +/*! + * \brief Return true if the client requires the frontend to provide a game loop + * + * The game loop is a thread that calls RunFrame() in a loop at a rate + * determined by the playback speed and the client's FPS. + * + * \return true if the frontend should provide a game loop, false otherwise + */ +bool RequiresGameLoop(void); + +/*! + * \brief Run a single frame for add-ons that use a game loop + * + * \return the error, or GAME_ERROR_NO_ERROR if there was no error + */ +GAME_ERROR RunFrame(void); + +/*! + * \brief Reset the current game + * + * \return the error, or GAME_ERROR_NO_ERROR if the game was reset + */ +GAME_ERROR Reset(void); + +// --- Hardware rendering operations ------------------------------------------- + +/*! + * \brief Invalidates the current HW context and reinitializes GPU resources + * + * Any GL state is lost, and must not be deinitialized explicitly. + * + * \return the error, or GAME_ERROR_NO_ERROR if the HW context was reset + */ +GAME_ERROR HwContextReset(void); + +/*! + * \brief Called before the context is destroyed + * + * Resources can be deinitialized at this step. + * + * \return the error, or GAME_ERROR_NO_ERROR if the HW context was destroyed + */ +GAME_ERROR HwContextDestroy(void); + +// --- Input operations -------------------------------------------------------- + +/*! + * \brief Notify the add-on of a status change on an open port + * + * Ports can be opened using the OpenPort() callback + * + * \param port Non-negative for a joystick port, or GAME_INPUT_PORT value otherwise + * \param collected True if a controller was connected, false if disconnected + * \param controller The connected controller + */ +void UpdatePort(int port, bool connected, const game_controller* controller); + +/*! + * \brief Check if input is accepted for a feature on the controller + * + * If only a subset of the controller profile is used, this can return false + * for unsupported features to not absorb their input. + * + * If the entire controller profile is used, this should always return true. + * + * \param controller_id The ID of the controller profile + * \param feature_name The name of a feature in that profile + * \return true if input is accepted for the feature, false otherwise + */ +bool HasFeature(const char* controller_id, const char* feature_name); + +/*! + * \brief Notify the add-on of an input event + * + * \param event The input event + * + * \return true if the event was handled, false otherwise + */ +bool InputEvent(const game_input_event* event); + +// --- Serialization operations ------------------------------------------------ + +/*! + * \brief Get the number of bytes required to serialize the game + * + * \return the number of bytes, or 0 if serialization is not supported + */ +size_t SerializeSize(void); + +/*! + * \brief Serialize the state of the game + * + * \param data The buffer receiving the serialized game data + * \param size The size of the buffer + * + * \return the error, or GAME_ERROR_NO_ERROR if the game was serialized into the buffer + */ +GAME_ERROR Serialize(uint8_t* data, size_t size); + +/*! + * \brief Deserialize the game from the given state + * + * \param data A buffer containing the game's new state + * \param size The size of the buffer + * + * \return the error, or GAME_ERROR_NO_ERROR if the game deserialized + */ +GAME_ERROR Deserialize(const uint8_t* data, size_t size); + +// --- Cheat operations -------------------------------------------------------- + +/*! + * \brief Reset the cheat system + * + * \return the error, or GAME_ERROR_NO_ERROR if the cheat system was reset + */ +GAME_ERROR CheatReset(void); + +/*! + * \brief Get a region of memory + * + * \param type The type of memory to retrieve + * \param data Set to the region of memory; must remain valid until UnloadGame() is called + * \param size Set to the size of the region of memory + * + * \return the error, or GAME_ERROR_NO_ERROR if data was set to a valid buffer + */ +GAME_ERROR GetMemory(GAME_MEMORY type, const uint8_t** data, size_t* size); + +/*! + * \brief Set a cheat code + * + * \param index + * \param enabled + * \param code + * + * \return the error, or GAME_ERROR_NO_ERROR if the cheat was set + */ +GAME_ERROR SetCheat(unsigned int index, bool enabled, const char* code); + +// --- Add-on helper implementation -------------------------------------------- + +/*! + * \brief Called by Kodi to assign the function pointers of this add-on to pClient + * + * Note that get_addon() is defined here, so it will be available in all + * compiled game clients. + */ +void __declspec(dllexport) get_addon(GameClient* pClient) +{ + pClient->GetGameAPIVersion = GetGameAPIVersion; + pClient->GetMininumGameAPIVersion = GetMininumGameAPIVersion; + pClient->LoadGame = LoadGame; + pClient->LoadGameSpecial = LoadGameSpecial; + pClient->LoadStandalone = LoadStandalone; + pClient->UnloadGame = UnloadGame; + pClient->GetGameInfo = GetGameInfo; + pClient->GetRegion = GetRegion; + pClient->RequiresGameLoop = RequiresGameLoop; + pClient->RunFrame = RunFrame; + pClient->Reset = Reset; + pClient->HwContextReset = HwContextReset; + pClient->HwContextDestroy = HwContextDestroy; + pClient->UpdatePort = UpdatePort; + pClient->HasFeature = HasFeature; + pClient->InputEvent = InputEvent; + pClient->SerializeSize = SerializeSize; + pClient->Serialize = Serialize; + pClient->Deserialize = Deserialize; + pClient->CheatReset = CheatReset; + pClient->GetMemory = GetMemory; + pClient->SetCheat = SetCheat; +} + +#ifdef __cplusplus +} +#endif + +#endif // KODI_GAME_DLL_H_ diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h new file mode 100644 index 0000000000..fa2762d87d --- /dev/null +++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h @@ -0,0 +1,487 @@ +/* + * Copyright (C) 2014-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#ifndef KODI_GAME_TYPES_H_ +#define KODI_GAME_TYPES_H_ + +/* current game API version */ +#define GAME_API_VERSION "1.0.28" + +/* min. game API version */ +#define GAME_MIN_API_VERSION "1.0.28" + +#include <stddef.h> +#include <stdint.h> + +#ifdef TARGET_WINDOWS + #include <windows.h> +#else + #ifndef __cdecl + #define __cdecl + #endif + #ifndef __declspec + #define __declspec(X) + #endif +#endif + +#undef ATTRIBUTE_PACKED +#undef PRAGMA_PACK_BEGIN +#undef PRAGMA_PACK_END + +#if defined(__GNUC__) + #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) + #define ATTRIBUTE_PACKED __attribute__ ((packed)) + #define PRAGMA_PACK 0 + #endif +#endif + +#if !defined(ATTRIBUTE_PACKED) + #define ATTRIBUTE_PACKED + #define PRAGMA_PACK 1 +#endif + +#ifdef BUILD_KODI_ADDON +#include "XBMC_vkeys.h" +#else +#include "input/XBMC_vkeys.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/*! Game add-on error codes */ +typedef enum GAME_ERROR +{ + GAME_ERROR_NO_ERROR, // no error occurred + GAME_ERROR_UNKNOWN, // an unknown error occurred + GAME_ERROR_NOT_IMPLEMENTED, // the method that the frontend called is not implemented + GAME_ERROR_REJECTED, // the command was rejected by the game client + GAME_ERROR_INVALID_PARAMETERS, // the parameters of the method that was called are invalid for this operation + GAME_ERROR_FAILED, // the command failed + GAME_ERROR_NOT_LOADED, // no game is loaded + GAME_ERROR_RESTRICTED, // game requires restricted resources +} GAME_ERROR; + +typedef enum GAME_STREAM_TYPE +{ + GAME_STREAM_UNKNOWN, + GAME_STREAM_AUDIO, + GAME_STREAM_VIDEO, +} GAME_STREAM_TYPE; + +typedef enum GAME_PIXEL_FORMAT +{ + GAME_PIXEL_FORMAT_UNKNOWN, + GAME_PIXEL_FORMAT_YUV420P, + GAME_PIXEL_FORMAT_0RGB8888, + GAME_PIXEL_FORMAT_RGB565, + GAME_PIXEL_FORMAT_0RGB1555, +} GAME_PIXEL_FORMAT; + +typedef enum GAME_VIDEO_CODEC +{ + GAME_VIDEO_CODEC_UNKNOWN, + GAME_VIDEO_CODEC_H264, + GAME_VIDEO_CODEC_THEORA, +} GAME_VIDEO_CODEC; + +typedef enum GAME_VIDEO_ROTATION // Counter-clockwise +{ + GAME_VIDEO_ROTATION_0, + GAME_VIDEO_ROTATION_90, + GAME_VIDEO_ROTATION_180, + GAME_VIDEO_ROTATION_270, +} GAME_VIDEO_ROTATION; + +typedef enum GAME_PCM_FORMAT +{ + GAME_PCM_FORMAT_UNKNOWN, + GAME_PCM_FORMAT_S16NE, +} GAME_PCM_FORMAT; + +typedef enum GAME_AUDIO_CODEC +{ + GAME_AUDIO_CODEC_UNKNOWN, + GAME_AUDIO_CODEC_OPUS, +} GAME_AUDIO_CODEC; + +typedef enum GAME_AUDIO_CHANNEL +{ + GAME_CH_NULL, // Channel list terminator + GAME_CH_FL, + GAME_CH_FR, + GAME_CH_FC, + GAME_CH_LFE, + GAME_CH_BL, + GAME_CH_BR, + GAME_CH_FLOC, + GAME_CH_FROC, + GAME_CH_BC, + GAME_CH_SL, + GAME_CH_SR, + GAME_CH_TFL, + GAME_CH_TFR, + GAME_CH_TFC, + GAME_CH_TC, + GAME_CH_TBL, + GAME_CH_TBR, + GAME_CH_TBC, + GAME_CH_BLOC, + GAME_CH_BROC, +} GAME_AUDIO_CHANNEL; + +// TODO +typedef enum GAME_HW_FRAME_BUFFER +{ + GAME_HW_FRAME_BUFFER_VALID, // Pass this to game_video_refresh if rendering to hardware + GAME_HW_FRAME_BUFFER_DUPLICATE, // Passing NULL to game_video_refresh is still a frame dupe as normal + GAME_HW_FRAME_BUFFER_RENDER, +} GAME_HW_FRAME_BUFFER; + +typedef enum GAME_HW_CONTEXT_TYPE +{ + GAME_HW_CONTEXT_NONE, + GAME_HW_CONTEXT_OPENGL, // OpenGL 2.x. Latest version available before 3.x+. Driver can choose to use latest compatibility context + GAME_HW_CONTEXT_OPENGLES2, // GLES 2.0 + GAME_HW_CONTEXT_OPENGL_CORE, // Modern desktop core GL context. Use major/minor fields to set GL version + GAME_HW_CONTEXT_OPENGLES3, // GLES 3.0 +} GAME_HW_CONTEXT_TYPE; + +typedef enum GAME_INPUT_PORT +{ + GAME_INPUT_PORT_JOYSTICK_START = 0, // Non-negative values are for joystick ports + GAME_INPUT_PORT_KEYBOARD = -1, + GAME_INPUT_PORT_MOUSE = -2, +} GAME_INPUT_PORT; + +typedef enum GAME_INPUT_EVENT_SOURCE +{ + GAME_INPUT_EVENT_DIGITAL_BUTTON, + GAME_INPUT_EVENT_ANALOG_BUTTON, + GAME_INPUT_EVENT_ANALOG_STICK, + GAME_INPUT_EVENT_ACCELEROMETER, + GAME_INPUT_EVENT_KEY, + GAME_INPUT_EVENT_RELATIVE_POINTER, + GAME_INPUT_EVENT_ABSOLUTE_POINTER, + GAME_INPUT_EVENT_MOTOR, +} GAME_INPUT_EVENT_SOURCE; + +typedef enum GAME_KEY_MOD +{ + GAME_KEY_MOD_NONE = 0x00, + + GAME_KEY_MOD_SHIFT = 0x01, + GAME_KEY_MOD_CTRL = 0x02, + GAME_KEY_MOD_ALT = 0x04, + GAME_KEY_MOD_RALT = 0x08, + GAME_KEY_MOD_META = 0x10, + + GAME_KEY_MOD_NUMLOCK = 0x20, + GAME_KEY_MOD_CAPSLOCK = 0x40, + GAME_KEY_MOD_SCROLLOCK = 0x80, +} GAME_KEY_MOD; + +/*! Returned from game_get_region() */ +typedef enum GAME_REGION +{ + GAME_REGION_UNKNOWN, + GAME_REGION_NTSC, + GAME_REGION_PAL, +} GAME_REGION; + +/*! +* Special game types passed into game_load_game_special(). Only used when +* multiple ROMs are required. +*/ +typedef enum SPECIAL_GAME_TYPE +{ + SPECIAL_GAME_TYPE_BSX, + SPECIAL_GAME_TYPE_BSX_SLOTTED, + SPECIAL_GAME_TYPE_SUFAMI_TURBO, + SPECIAL_GAME_TYPE_SUPER_GAME_BOY, +} SPECIAL_GAME_TYPE; + +typedef enum GAME_MEMORY +{ + /*! + * Passed to game_get_memory_data/size(). If the memory type doesn't apply + * to the implementation NULL/0 can be returned. + */ + GAME_MEMORY_MASK = 0xff, + + /*! + * Regular save ram. This ram is usually found on a game cartridge, backed + * up by a battery. If save game data is too complex for a single memory + * buffer, the SYSTEM_DIRECTORY environment callback can be used. + */ + GAME_MEMORY_SAVE_RAM = 0, + + /*! + * Some games have a built-in clock to keep track of time. This memory is + * usually just a couple of bytes to keep track of time. + */ + GAME_MEMORY_RTC = 1, + + /*! System ram lets a frontend peek into a game systems main RAM */ + GAME_MEMORY_SYSTEM_RAM = 2, + + /*! Video ram lets a frontend peek into a game systems video RAM (VRAM) */ + GAME_MEMORY_VIDEO_RAM = 3, + + /*! Special memory types */ + GAME_MEMORY_SNES_BSX_RAM = ((1 << 8) | GAME_MEMORY_SAVE_RAM), + GAME_MEMORY_SNES_BSX_PRAM = ((2 << 8) | GAME_MEMORY_SAVE_RAM), + GAME_MEMORY_SNES_SUFAMI_TURBO_A_RAM= ((3 << 8) | GAME_MEMORY_SAVE_RAM), + GAME_MEMORY_SNES_SUFAMI_TURBO_B_RAM= ((4 << 8) | GAME_MEMORY_SAVE_RAM), + GAME_MEMORY_SNES_GAME_BOY_RAM = ((5 << 8) | GAME_MEMORY_SAVE_RAM), + GAME_MEMORY_SNES_GAME_BOY_RTC = ((6 << 8) | GAME_MEMORY_RTC), +} GAME_MEMORY; + +/*! ID values for SIMD CPU features */ +typedef enum GAME_SIMD +{ + GAME_SIMD_SSE = (1 << 0), + GAME_SIMD_SSE2 = (1 << 1), + GAME_SIMD_VMX = (1 << 2), + GAME_SIMD_VMX128 = (1 << 3), + GAME_SIMD_AVX = (1 << 4), + GAME_SIMD_NEON = (1 << 5), + GAME_SIMD_SSE3 = (1 << 6), + GAME_SIMD_SSSE3 = (1 << 7), + GAME_SIMD_MMX = (1 << 8), + GAME_SIMD_MMXEXT = (1 << 9), + GAME_SIMD_SSE4 = (1 << 10), + GAME_SIMD_SSE42 = (1 << 11), + GAME_SIMD_AVX2 = (1 << 12), + GAME_SIMD_VFPU = (1 << 13), +} GAME_SIMD; + +typedef enum GAME_ROTATION +{ + GAME_ROTATION_0_CW, + GAME_ROTATION_90_CW, + GAME_ROTATION_180_CW, + GAME_ROTATION_270_CW, +} GAME_ROTATION; + +typedef struct game_controller +{ + const char* controller_id; + unsigned int digital_button_count; + unsigned int analog_button_count; + unsigned int analog_stick_count; + unsigned int accelerometer_count; + unsigned int key_count; + unsigned int rel_pointer_count; + unsigned int abs_pointer_count; + unsigned int motor_count; +} ATTRIBUTE_PACKED game_controller; + +typedef struct game_digital_button_event +{ + bool pressed; +} ATTRIBUTE_PACKED game_digital_button_event; + +typedef struct game_analog_button_event +{ + float magnitude; +} ATTRIBUTE_PACKED game_analog_button_event; + +typedef struct game_analog_stick_event +{ + float x; + float y; +} ATTRIBUTE_PACKED game_analog_stick_event; + +typedef struct game_accelerometer_event +{ + float x; + float y; + float z; +} ATTRIBUTE_PACKED game_accelerometer_event; + +typedef struct game_key_event +{ + bool pressed; + XBMCVKey character; + GAME_KEY_MOD modifiers; +} ATTRIBUTE_PACKED game_key_event; + +typedef struct game_rel_pointer_event +{ + int x; + int y; +} ATTRIBUTE_PACKED game_rel_pointer_event; + +typedef struct game_abs_pointer_event +{ + bool pressed; + float x; + float y; +} ATTRIBUTE_PACKED game_abs_pointer_event; + +typedef struct game_motor_event +{ + float magnitude; +} ATTRIBUTE_PACKED game_motor_event; + +typedef struct game_input_event +{ + GAME_INPUT_EVENT_SOURCE type; + int port; + const char* controller_id; + const char* feature_name; + union + { + struct game_digital_button_event digital_button; + struct game_analog_button_event analog_button; + struct game_analog_stick_event analog_stick; + struct game_accelerometer_event accelerometer; + struct game_key_event key; + struct game_rel_pointer_event rel_pointer; + struct game_abs_pointer_event abs_pointer; + struct game_motor_event motor; + }; +} ATTRIBUTE_PACKED game_input_event; + +struct game_geometry +{ + unsigned base_width; // Nominal video width of game + unsigned base_height; // Nominal video height of game + unsigned max_width; // Maximum possible width of game + unsigned max_height; // Maximum possible height of game + float aspect_ratio; // Nominal aspect ratio of game. If aspect_ratio is <= 0.0, + // an aspect ratio of base_width / base_height is assumed. + // A frontend could override this setting if desired. +}; + +struct game_system_timing +{ + double fps; // FPS of video content. + double sample_rate; // Sampling rate of audio. +}; + +struct game_system_av_info +{ + struct game_geometry geometry; + struct game_system_timing timing; +}; + +typedef void (*game_proc_address_t)(void); + +struct game_hw_info +{ + GAME_HW_CONTEXT_TYPE context_type; // Which API to use. Set by game client + bool depth; // Set if render buffers should have depth component attached + bool stencil; // Set if stencil buffers should be attached + // If depth and stencil are true, a packed 24/8 buffer will be added. Only attaching stencil is invalid and will be ignored + bool bottom_left_origin; // Use conventional bottom-left origin convention. Is false, standard top-left origin semantics are used + unsigned version_major; // Major version number for core GL context + unsigned version_minor; // Minor version number for core GL context + bool cache_context; // If this is true, the frontend will go very far to avoid resetting context in scenarios like toggling fullscreen, etc. + // The reset callback might still be called in extreme situations such as if the context is lost beyond recovery + // For optimal stability, set this to false, and allow context to be reset at any time + bool debug_context; // Creates a debug context +}; + +/*! Properties passed to the ADDON_Create() method of a game client */ +typedef struct game_client_properties +{ + /*! + * The path of the game client being loaded. + */ + const char* game_client_dll_path; + + /*! + * Paths to proxy DLLs used to load the game client. + */ + const char** proxy_dll_paths; + + /*! + * Number of proxy DLL paths provided. + */ + unsigned int proxy_dll_count; + + /*! + * The "system" directories of the frontend. These directories can be used to + * store system-specific ROMs such as BIOSes, configuration data, etc. + */ + const char** resource_directories; + + /*! + * Number of resource directories provided + */ + unsigned int resource_directory_count; + + /*! + * The writable directory of the frontend. This directory can be used to store + * SRAM, memory cards, high scores, etc, if the game client cannot use the + * regular memory interface, GetMemoryData(). + */ + const char* profile_directory; + + /*! + * The value of the <supports_vfs> property from addon.xml + */ + bool supports_vfs; + + /*! + * The extensions in the <extensions> property from addon.xml + */ + const char** extensions; + + /*! + * Number of extensions provided + */ + unsigned int extension_count; +} game_client_properties; + +/*! Structure to transfer the methods from kodi_game_dll.h to Kodi */ +typedef struct GameClient +{ + const char* (__cdecl* GetGameAPIVersion)(void); + const char* (__cdecl* GetMininumGameAPIVersion)(void); + GAME_ERROR (__cdecl* LoadGame)(const char*); + GAME_ERROR (__cdecl* LoadGameSpecial)(SPECIAL_GAME_TYPE, const char**, size_t); + GAME_ERROR (__cdecl* LoadStandalone)(void); + GAME_ERROR (__cdecl* UnloadGame)(void); + GAME_ERROR (__cdecl* GetGameInfo)(game_system_av_info*); + GAME_REGION (__cdecl* GetRegion)(void); + bool (__cdecl* RequiresGameLoop)(void); + GAME_ERROR (__cdecl* RunFrame)(void); + GAME_ERROR (__cdecl* Reset)(void); + GAME_ERROR (__cdecl* HwContextReset)(void); + GAME_ERROR (__cdecl* HwContextDestroy)(void); + void (__cdecl* UpdatePort)(int, bool, const game_controller*); + bool (__cdecl* HasFeature)(const char* controller_id, const char* feature_name); + bool (__cdecl* InputEvent)(const game_input_event*); + size_t (__cdecl* SerializeSize)(void); + GAME_ERROR (__cdecl* Serialize)(uint8_t*, size_t); + GAME_ERROR (__cdecl* Deserialize)(const uint8_t*, size_t); + GAME_ERROR (__cdecl* CheatReset)(void); + GAME_ERROR (__cdecl* GetMemory)(GAME_MEMORY, const uint8_t**, size_t*); + GAME_ERROR (__cdecl* SetCheat)(unsigned int, bool, const char*); +} GameClient; + +#ifdef __cplusplus +} +#endif + +#endif // KODI_GAME_TYPES_H_ diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_game.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_game.h new file mode 100644 index 0000000000..b5a46dd223 --- /dev/null +++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/libKODI_game.h @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2014-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "libXBMC_addon.h" +#include "kodi_game_callbacks.h" + +#include <string> +#include <stdio.h> + +#if defined(ANDROID) + #include <sys/stat.h> +#endif + +#ifdef _WIN32 + #define GAME_HELPER_DLL "\\library.kodi.game\\libKODI_game" ADDON_HELPER_EXT +#else + #define GAME_HELPER_DLL_NAME "libKODI_game-" ADDON_HELPER_ARCH ADDON_HELPER_EXT + #define GAME_HELPER_DLL "/library.kodi.game/" GAME_HELPER_DLL_NAME +#endif + +#define GAME_REGISTER_SYMBOL(dll, functionPtr) \ + CHelper_libKODI_game::RegisterSymbol(dll, functionPtr, #functionPtr) + +class CHelper_libKODI_game +{ +public: + CHelper_libKODI_game(void) : + GAME_register_me(nullptr), + GAME_unregister_me(nullptr), + GAME_close_game(nullptr), + GAME_open_pixel_stream(nullptr), + GAME_open_video_stream(nullptr), + GAME_open_pcm_stream(nullptr), + GAME_open_audio_stream(nullptr), + GAME_add_stream_data(nullptr), + GAME_close_stream(nullptr), + GAME_enable_hardware_rendering(nullptr), + GAME_hw_get_current_framebuffer(nullptr), + GAME_hw_get_proc_address(nullptr), + GAME_render_frame(nullptr), + GAME_open_port(nullptr), + GAME_close_port(nullptr), + GAME_input_event(nullptr), + m_handle(nullptr), + m_callbacks(nullptr), + m_libKODI_game(nullptr) + { + } + + ~CHelper_libKODI_game(void) + { + if (m_libKODI_game) + { + GAME_unregister_me(m_handle, m_callbacks); + dlclose(m_libKODI_game); + } + } + + template <typename T> + static bool RegisterSymbol(void* dll, T& functionPtr, const char* strFunctionPtr) + { + return (functionPtr = (T)dlsym(dll, strFunctionPtr)) != NULL; + } + + /*! + * @brief Resolve all callback methods + * @param handle Pointer to the add-on + * @return True when all methods were resolved, false otherwise. + */ + bool RegisterMe(void* handle) + { + m_handle = handle; + + std::string libBasePath; + libBasePath = ((cb_array*)m_handle)->libBasePath; + libBasePath += GAME_HELPER_DLL; + +#if defined(ANDROID) + struct stat st; + if (stat(libBasePath.c_str(),&st) != 0) + { + std::string tempbin = getenv("XBMC_ANDROID_LIBS"); + libBasePath = tempbin + "/" + GAME_HELPER_DLL_NAME; + } +#endif + + m_libKODI_game = dlopen(libBasePath.c_str(), RTLD_LAZY); + if (m_libKODI_game == NULL) + { + fprintf(stderr, "Unable to load %s\n", dlerror()); + return false; + } + + try + { + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_register_me)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_unregister_me)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_close_game)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_open_pixel_stream)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_open_video_stream)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_open_pcm_stream)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_open_audio_stream)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_add_stream_data)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_close_stream)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_enable_hardware_rendering)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_hw_get_current_framebuffer)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_hw_get_proc_address)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_render_frame)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_open_port)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_close_port)) throw false; + if (!GAME_REGISTER_SYMBOL(m_libKODI_game, GAME_input_event)) throw false; + } + catch (const bool& bSuccess) + { + fprintf(stderr, "ERROR: Unable to assign function %s\n", dlerror()); + return bSuccess; + } + + m_callbacks = GAME_register_me(m_handle); + return m_callbacks != NULL; + } + + void CloseGame(void) + { + return GAME_close_game(m_handle, m_callbacks); + } + + bool OpenPixelStream(GAME_PIXEL_FORMAT format, unsigned int width, unsigned int height, GAME_VIDEO_ROTATION rotation) + { + return GAME_open_pixel_stream(m_handle, m_callbacks, format, width, height, rotation) == 0; + } + + bool OpenVideoStream(GAME_VIDEO_CODEC codec) + { + return GAME_open_video_stream(m_handle, m_callbacks, codec) == 0; + } + + bool OpenPCMStream(GAME_PCM_FORMAT format, const GAME_AUDIO_CHANNEL* channel_map) + { + return GAME_open_pcm_stream(m_handle, m_callbacks, format, channel_map) == 0; + } + + bool OpenAudioStream(GAME_AUDIO_CODEC codec, const GAME_AUDIO_CHANNEL* channel_map) + { + return GAME_open_audio_stream(m_handle, m_callbacks, codec, channel_map) == 0; + } + + void AddStreamData(GAME_STREAM_TYPE stream, const uint8_t* data, unsigned int size) + { + GAME_add_stream_data(m_handle, m_callbacks, stream, data, size); + } + + void CloseStream(GAME_STREAM_TYPE stream) + { + GAME_close_stream(m_handle, m_callbacks, stream); + } + + void EnableHardwareRendering(const struct game_hw_info* hw_info) + { + return GAME_enable_hardware_rendering(m_handle, m_callbacks, hw_info); + } + + uintptr_t HwGetCurrentFramebuffer(void) + { + return GAME_hw_get_current_framebuffer(m_handle, m_callbacks); + } + + game_proc_address_t HwGetProcAddress(const char* sym) + { + return GAME_hw_get_proc_address(m_handle, m_callbacks, sym); + } + + void RenderFrame() + { + return GAME_render_frame(m_handle, m_callbacks); + } + + bool OpenPort(unsigned int port) + { + return GAME_open_port(m_handle, m_callbacks, port); + } + + void ClosePort(unsigned int port) + { + return GAME_close_port(m_handle, m_callbacks, port); + } + + bool InputEvent(const game_input_event& event) + { + return GAME_input_event(m_handle, m_callbacks, &event); + } + +protected: + CB_GameLib* (*GAME_register_me)(void* handle); + void (*GAME_unregister_me)(void* handle, CB_GameLib* cb); + void (*GAME_close_game)(void* handle, CB_GameLib* cb); + int (*GAME_open_pixel_stream)(void* handle, CB_GameLib* cb, GAME_PIXEL_FORMAT, unsigned int, unsigned int, GAME_VIDEO_ROTATION); + int (*GAME_open_video_stream)(void* handle, CB_GameLib* cb, GAME_VIDEO_CODEC); + int (*GAME_open_pcm_stream)(void* handle, CB_GameLib* cb, GAME_PCM_FORMAT, const GAME_AUDIO_CHANNEL*); + int (*GAME_open_audio_stream)(void* handle, CB_GameLib* cb, GAME_AUDIO_CODEC, const GAME_AUDIO_CHANNEL*); + int (*GAME_add_stream_data)(void* handle, CB_GameLib* cb, GAME_STREAM_TYPE, const uint8_t*, unsigned int); + int (*GAME_close_stream)(void* handle, CB_GameLib* cb, GAME_STREAM_TYPE); + void (*GAME_enable_hardware_rendering)(void* handle, CB_GameLib* cb, const struct game_hw_info*); + uintptr_t (*GAME_hw_get_current_framebuffer)(void* handle, CB_GameLib* cb); + game_proc_address_t (*GAME_hw_get_proc_address)(void* handle, CB_GameLib* cb, const char*); + void (*GAME_render_frame)(void* handle, CB_GameLib* cb); + bool (*GAME_open_port)(void* handle, CB_GameLib* cb, unsigned int); + void (*GAME_close_port)(void* handle, CB_GameLib* cb, unsigned int); + bool (*GAME_input_event)(void* handle, CB_GameLib* cb, const game_input_event* event); + +private: + void* m_handle; + CB_GameLib* m_callbacks; + void* m_libKODI_game; + + struct cb_array + { + const char* libBasePath; + }; +}; diff --git a/xbmc/filesystem/AddonsDirectory.cpp b/xbmc/filesystem/AddonsDirectory.cpp index a89cc89dd3..d150935f64 100644 --- a/xbmc/filesystem/AddonsDirectory.cpp +++ b/xbmc/filesystem/AddonsDirectory.cpp @@ -28,11 +28,15 @@ #include "interfaces/generic/ScriptInvocationManager.h" #include "FileItem.h" #include "addons/AddonInstaller.h" +#include "addons/BinaryAddonCache.h" #include "addons/PluginSource.h" #include "addons/RepositoryUpdater.h" #include "dialogs/GUIDialogOK.h" +#include "games/addons/GameClient.h" +#include "games/GameUtils.h" #include "guilib/TextureManager.h" #include "File.h" +#include "ServiceBroker.h" #include "settings/Settings.h" #include "SpecialProtocol.h" #include "utils/URIUtils.h" @@ -51,6 +55,11 @@ CAddonsDirectory::~CAddonsDirectory(void) {} const auto CATEGORY_INFO_PROVIDERS = "category.infoproviders"; const auto CATEGORY_LOOK_AND_FEEL = "category.lookandfeel"; const auto CATEGORY_GAME_ADDONS = "category.gameaddons"; +const auto CATEGORY_EMULATORS = "category.emulators"; +const auto CATEGORY_STANDALONE_GAMES = "category.standalonegames"; +const auto CATEGORY_GAME_PROVIDERS = "category.gameproviders"; +const auto CATEGORY_GAME_RESOURCES = "category.gameresources"; +const auto CATEGORY_GAME_SUPPORT_ADDONS = "category.gamesupport"; const std::set<TYPE> dependencyTypes = { ADDON_SCRAPER_LIBRARY, @@ -77,6 +86,9 @@ const std::set<TYPE> lookAndFeelTypes = { const std::set<TYPE> gameTypes = { ADDON_GAME_CONTROLLER, + ADDON_GAMEDLL, + ADDON_GAME, + ADDON_RESOURCE_GAMES, }; static bool IsInfoProviderType(TYPE type) @@ -104,9 +116,40 @@ static bool IsGameType(TYPE type) return gameTypes.find(type) != gameTypes.end(); } +static bool IsStandaloneGame(const AddonPtr& addon) +{ + return GAME::CGameUtils::IsStandaloneGame(addon); +} + +static bool IsEmulator(const AddonPtr& addon) +{ + return addon->Type() == ADDON_GAMEDLL && std::static_pointer_cast<GAME::CGameClient>(addon)->SupportsPath(); +} + +static bool IsGameProvider(const AddonPtr& addon) +{ + return addon->Type() == ADDON_PLUGIN && addon->IsType(ADDON_GAME); +} + +static bool IsGameResource(const AddonPtr& addon) +{ + return addon->Type() == ADDON_RESOURCE_GAMES; +} + +static bool IsGameSupportAddon(const AddonPtr& addon) +{ + return addon->Type() == ADDON_GAMEDLL && + !std::static_pointer_cast<GAME::CGameClient>(addon)->SupportsPath() && + !std::static_pointer_cast<GAME::CGameClient>(addon)->SupportsStandalone(); +} + static bool IsGameAddon(const AddonPtr& addon) { - return IsGameType(addon->Type()); + return IsGameType(addon->Type()) || + IsStandaloneGame(addon) || + IsGameProvider(addon) || + IsGameResource(addon) || + IsGameSupportAddon(addon); } static bool IsDependecyType(TYPE type) @@ -161,6 +204,113 @@ static void GenerateTypeListing(const CURL& path, const std::set<TYPE>& types, } } +// Creates categories for game add-ons, if we have any game add-ons +static void GenerateGameListing(const CURL& path, const VECADDONS& addons, CFileItemList& items) +{ + // Game controllers + for (const auto& addon : addons) + { + if (addon->Type() == ADDON_GAME_CONTROLLER) + { + CFileItemPtr item(new CFileItem(TranslateType(ADDON_GAME_CONTROLLER, true))); + CURL itemPath = path; + itemPath.SetFileName(TranslateType(ADDON_GAME_CONTROLLER, false)); + item->SetPath(itemPath.Get()); + item->m_bIsFolder = true; + std::string thumb = GetIcon(ADDON_GAME_CONTROLLER); + if (!thumb.empty() && g_TextureManager.HasTexture(thumb)) + item->SetArt("thumb", thumb); + items.Add(item); + break; + } + } + // Emulators + for (const auto& addon : addons) + { + if (IsEmulator(addon)) + { + CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35207))); // Emulators + CURL itemPath = path; + itemPath.SetFileName(CATEGORY_EMULATORS); + item->SetPath(itemPath.Get()); + item->m_bIsFolder = true; + std::string thumb = GetIcon(ADDON_GAMEDLL); + if (!thumb.empty() && g_TextureManager.HasTexture(thumb)) + item->SetArt("thumb", thumb); + items.Add(item); + break; + } + } + // Standalone games + for (const auto& addon : addons) + { + if (IsStandaloneGame(addon)) + { + CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35208))); // Standalone games + CURL itemPath = path; + itemPath.SetFileName(CATEGORY_STANDALONE_GAMES); + item->SetPath(itemPath.Get()); + item->m_bIsFolder = true; + std::string thumb = GetIcon(ADDON_GAMEDLL); + if (!thumb.empty() && g_TextureManager.HasTexture(thumb)) + item->SetArt("thumb", thumb); + items.Add(item); + break; + } + } + // Game providers + for (const auto& addon : addons) + { + if (IsGameProvider(addon)) + { + CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35220))); // Game providers + CURL itemPath = path; + itemPath.SetFileName(CATEGORY_GAME_PROVIDERS); + item->SetPath(itemPath.Get()); + item->m_bIsFolder = true; + std::string thumb = GetIcon(ADDON_GAMEDLL); + if (!thumb.empty() && g_TextureManager.HasTexture(thumb)) + item->SetArt("thumb", thumb); + items.Add(item); + break; + } + } + // Game resources + for (const auto& addon : addons) + { + if (IsGameResource(addon)) + { + CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35209))); // Game resources + CURL itemPath = path; + itemPath.SetFileName(CATEGORY_GAME_RESOURCES); + item->SetPath(itemPath.Get()); + item->m_bIsFolder = true; + std::string thumb = GetIcon(ADDON_GAMEDLL); + if (!thumb.empty() && g_TextureManager.HasTexture(thumb)) + item->SetArt("thumb", thumb); + items.Add(item); + break; + } + } + // Game support add-ons + for (const auto& addon : addons) + { + if (IsGameSupportAddon(addon)) + { + CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35216))); // Support add-ons + CURL itemPath = path; + itemPath.SetFileName(CATEGORY_GAME_SUPPORT_ADDONS); + item->SetPath(itemPath.Get()); + item->m_bIsFolder = true; + std::string thumb = GetIcon(ADDON_GAMEDLL); + if (!thumb.empty() && g_TextureManager.HasTexture(thumb)) + item->SetArt("thumb", thumb); + items.Add(item); + break; + } + } +} + //Creates the top-level category list static void GenerateMainCategoryListing(const CURL& path, const VECADDONS& addons, CFileItemList& items) @@ -187,10 +337,10 @@ static void GenerateMainCategoryListing(const CURL& path, const VECADDONS& addon } if (std::any_of(addons.begin(), addons.end(), IsGameAddon)) { - CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35049))); // Game add-ons + CFileItemPtr item(new CFileItem(TranslateType(ADDON_GAME, true))); item->SetPath(URIUtils::AddFileToFolder(path.Get(), CATEGORY_GAME_ADDONS)); item->m_bIsFolder = true; - const std::string thumb = "DefaultGameAddons.png"; + const std::string thumb = GetIcon(ADDON_GAME); if (g_TextureManager.HasTexture(thumb)) item->SetArt("thumb", thumb); items.Add(item); @@ -200,7 +350,7 @@ static void GenerateMainCategoryListing(const CURL& path, const VECADDONS& addon for (unsigned int i = ADDON_UNKNOWN + 1; i < ADDON_MAX - 1; ++i) { const TYPE type = (TYPE)i; - if (!IsInfoProviderType(type) && !IsLookAndFeelType(type) && !IsDependecyType(type)) + if (!IsInfoProviderType(type) && !IsLookAndFeelType(type) && !IsDependecyType(type) && !IsGameType(type)) uncategorized.insert(static_cast<TYPE>(i)); } GenerateTypeListing(path, uncategorized, addons, items); @@ -225,9 +375,44 @@ static void GenerateCategoryListing(const CURL& path, VECADDONS& addons, } else if (category == CATEGORY_GAME_ADDONS) { - items.SetProperty("addoncategory", g_localizeStrings.Get(35049)); // Game add-ons - items.SetLabel(g_localizeStrings.Get(35049)); // Game add-ons - GenerateTypeListing(path, gameTypes, addons, items); + items.SetProperty("addoncategory", TranslateType(ADDON_GAME, true)); + items.SetLabel(TranslateType(ADDON_GAME, true)); + GenerateGameListing(path, addons, items); + } + else if (category == CATEGORY_EMULATORS) + { + items.SetProperty("addoncategory", g_localizeStrings.Get(35207)); // Emulators + addons.erase(std::remove_if(addons.begin(), addons.end(), + [](const AddonPtr& addon){ return !IsEmulator(addon); }), addons.end()); + CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35207)); // Emulators + } + else if (category == CATEGORY_STANDALONE_GAMES) + { + items.SetProperty("addoncategory", g_localizeStrings.Get(35208)); // Standalone games + addons.erase(std::remove_if(addons.begin(), addons.end(), + [](const AddonPtr& addon){ return !IsStandaloneGame(addon); }), addons.end()); + CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35208)); // Standalone games + } + else if (category == CATEGORY_GAME_PROVIDERS) + { + items.SetProperty("addoncategory", g_localizeStrings.Get(35220)); // Game providers + addons.erase(std::remove_if(addons.begin(), addons.end(), + [](const AddonPtr& addon){ return !IsGameProvider(addon); }), addons.end()); + CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35220)); // Game providers + } + else if (category == CATEGORY_GAME_RESOURCES) + { + items.SetProperty("addoncategory", g_localizeStrings.Get(35209)); // Game resources + addons.erase(std::remove_if(addons.begin(), addons.end(), + [](const AddonPtr& addon){ return !IsGameResource(addon); }), addons.end()); + CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35209)); // Game resources + } + else if (category == CATEGORY_GAME_SUPPORT_ADDONS) + { + items.SetProperty("addoncategory", g_localizeStrings.Get(35216)); // Support add-ons + addons.erase(std::remove_if(addons.begin(), addons.end(), + [](const AddonPtr& addon) { return !IsGameSupportAddon(addon); }), addons.end()); + CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35216)); // Support add-ons } else { // fallback to addon type @@ -572,6 +757,8 @@ bool CAddonsDirectory::GetDirectory(const CURL& url, CFileItemList &items) std::string type = path.GetFileName(); if (type == "video" || type == "audio" || type == "image" || type == "executable") return Browse(CURL("addons://all/xbmc.addon." + type), items); + else if (type == "game") + return Browse(CURL("addons://all/category.gameaddons"), items); return false; } else @@ -681,6 +868,18 @@ bool CAddonsDirectory::GetScriptsAndPlugins(const std::string &content, VECADDON if (plugin && plugin->Provides(type)) addons.push_back(tempAddons[i]); } + tempAddons.clear(); + + if (type == CPluginSource::GAME) + { + CAddonMgr::GetInstance().GetAddons(tempAddons, ADDON_GAMEDLL); + for (auto& addon : tempAddons) + { + if (IsStandaloneGame(addon)) + addons.push_back(addon); + } + } + return true; } @@ -715,6 +914,12 @@ bool CAddonsDirectory::GetScriptsAndPlugins(const std::string &content, CFileIte path = "script://" + addon->ID(); break; } + case ADDON_GAMEDLL: + { + // Kodi fails to launch games with empty path from home screen + path = "game://" + addon->ID(); + break; + } default: break; } diff --git a/xbmc/games/CMakeLists.txt b/xbmc/games/CMakeLists.txt index b65d7f905e..759f0987b0 100644 --- a/xbmc/games/CMakeLists.txt +++ b/xbmc/games/CMakeLists.txt @@ -1,5 +1,8 @@ -set(SOURCES GameSettings.cpp) +set(SOURCES GameSettings.cpp + GameUtils.cpp) -set(HEADERS GameSettings.h) +set(HEADERS GameSettings.h + GameTypes.h + GameUtils.h) core_add_library(games) diff --git a/xbmc/games/GameSettings.cpp b/xbmc/games/GameSettings.cpp index 0396870f27..a72878249f 100644 --- a/xbmc/games/GameSettings.cpp +++ b/xbmc/games/GameSettings.cpp @@ -48,6 +48,12 @@ void CGameSettings::OnSettingChanged(const CSetting *setting) { PERIPHERALS::g_peripherals.TriggerDeviceScan(PERIPHERALS::PERIPHERAL_BUS_APPLICATION); } + else if (settingId == CSettings::SETTING_GAMES_ENABLEREWIND || + settingId == CSettings::SETTING_GAMES_REWINDTIME) + { + SetChanged(); + NotifyObservers(ObservableMessageSettingsChanged); + } } void CGameSettings::OnSettingAction(const CSetting *setting) diff --git a/xbmc/games/GameSettings.h b/xbmc/games/GameSettings.h index f805006010..753b8f1f13 100644 --- a/xbmc/games/GameSettings.h +++ b/xbmc/games/GameSettings.h @@ -20,13 +20,15 @@ #pragma once #include "settings/lib/ISettingCallback.h" +#include "utils/Observer.h" class CSetting; namespace GAME { -class CGameSettings : public ISettingCallback +class CGameSettings : public ISettingCallback, + public Observable { public: static CGameSettings& GetInstance(); diff --git a/xbmc/games/GameTypes.h b/xbmc/games/GameTypes.h new file mode 100644 index 0000000000..0421908260 --- /dev/null +++ b/xbmc/games/GameTypes.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2015-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include <memory> +#include <vector> + +namespace GAME +{ + + class CGameClient; + typedef std::shared_ptr<CGameClient> GameClientPtr; + typedef std::vector<GameClientPtr> GameClientVector; + +} diff --git a/xbmc/games/GameUtils.cpp b/xbmc/games/GameUtils.cpp new file mode 100644 index 0000000000..2226336b0c --- /dev/null +++ b/xbmc/games/GameUtils.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GameUtils.h" +#include "addons/Addon.h" +#include "addons/AddonManager.h" +#include "addons/BinaryAddonCache.h" +#include "dialogs/GUIDialogOK.h" +#include "games/addons/GameClient.h" +#include "games/dialogs/GUIDialogSelectGameClient.h" +#include "games/tags/GameInfoTag.h" +#include "filesystem/SpecialProtocol.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "FileItem.h" +#include "ServiceBroker.h" +#include "URL.h" + +#include <algorithm> + +using namespace GAME; + +GameClientPtr CGameUtils::OpenGameClient(const CFileItem& file) +{ + using namespace ADDON; + + GameClientPtr gameClient; + + // Get the game client ID from the game info tag + std::string gameClientId; + if (file.HasGameInfoTag()) + gameClientId = file.GetGameInfoTag()->GetGameClient(); + + // If the fileitem is an add-on, fall back to that + if (gameClientId.empty()) + { + if (file.HasAddonInfo() && file.GetAddonInfo()->Type() == ADDON::ADDON_GAMEDLL) + gameClientId = file.GetAddonInfo()->ID(); + } + + // Get game client by ID + if (!gameClientId.empty()) + { + CBinaryAddonCache& addonCache = CServiceBroker::GetBinaryAddonCache(); + AddonPtr addon = addonCache.GetAddonInstance(gameClientId, ADDON_GAMEDLL); + gameClient = std::static_pointer_cast<GAME::CGameClient>(addon); + } + + // Need to prompt the user if no game client was found + if (!gameClient) + { + GameClientVector candidates; + GameClientVector installable; + bool bHasVfsGameClient; + GetGameClients(file, candidates, installable, bHasVfsGameClient); + + if (candidates.empty() && installable.empty()) + { + int errorTextId = bHasVfsGameClient ? + 35214 : // "This game can only be played directly from a hard drive or partition. Compressed files must be extracted." + 35212; // "This game isn't compatible with any available emulators." + + // "Failed to play game" + CGUIDialogOK::ShowAndGetInput(CVariant{ 35210 }, CVariant{ errorTextId }); + } + else if (candidates.size() == 1 && installable.empty()) + { + // Only 1 option, avoid prompting the user + gameClient = candidates[0]; + } + else + { + CGUIDialogSelectGameClient::ShowAndGetGameClient(candidates, installable, gameClient); + } + } + + return gameClient; +} + +void CGameUtils::GetGameClients(const CFileItem& file, GameClientVector& candidates, GameClientVector& installable, bool& bHasVfsGameClient) +{ + using namespace ADDON; + + bHasVfsGameClient = false; + + // Try to resolve path to a local file, as not all game clients support VFS + CURL translatedUrl(CSpecialProtocol::TranslatePath(file.GetPath())); + + // Get local candidates + VECADDONS localAddons; + CBinaryAddonCache& addonCache = CServiceBroker::GetBinaryAddonCache(); + addonCache.GetAddons(localAddons, ADDON_GAMEDLL); + + bool bVfs = false; + GetGameClients(localAddons, translatedUrl, candidates, bVfs); + bHasVfsGameClient |= bVfs; + + // Get remote candidates + VECADDONS remoteAddons; + if (CAddonMgr::GetInstance().GetInstallableAddons(remoteAddons, ADDON_GAMEDLL)) + { + GetGameClients(remoteAddons, translatedUrl, installable, bVfs); + bHasVfsGameClient |= bVfs; + } + + // Sort by name + //! @todo Move to presentation code + auto SortByName = [](const GameClientPtr& lhs, const GameClientPtr& rhs) + { + std::string lhsName = lhs->Name(); + std::string rhsName = rhs->Name(); + + StringUtils::ToLower(lhsName); + StringUtils::ToLower(rhsName); + + return lhsName < rhsName; + }; + + std::sort(candidates.begin(), candidates.end(), SortByName); + std::sort(installable.begin(), installable.end(), SortByName); +} + +void CGameUtils::GetGameClients(const ADDON::VECADDONS& addons, const CURL& translatedUrl, GameClientVector& candidates, bool& bHasVfsGameClient) +{ + bHasVfsGameClient = false; + + const std::string extension = URIUtils::GetExtension(translatedUrl.Get()); + + const bool bIsLocalFile = (translatedUrl.GetProtocol() == "file" || + translatedUrl.GetProtocol().empty()); + + for (auto& addon : addons) + { + GameClientPtr gameClient = std::static_pointer_cast<CGameClient>(addon); + + // Filter by extension + if (!gameClient->IsExtensionValid(extension)) + continue; + + // Filter by VFS + if (!bIsLocalFile && !gameClient->SupportsVFS()) + { + bHasVfsGameClient = true; + continue; + } + + candidates.push_back(gameClient); + } +} + +bool CGameUtils::HasGameExtension(const std::string &path) +{ + using namespace ADDON; + + // Get filename from CURL so that top-level zip directories will become + // normal paths: + // + // zip://%2Fpath_to_zip_file.zip/ -> /path_to_zip_file.zip + // + std::string filename = CURL(path).GetFileNameWithoutPath(); + + // Get the file extension + std::string extension = URIUtils::GetExtension(filename); + if (extension.empty()) + return false; + + StringUtils::ToLower(extension); + + // Look for a game client that supports this extension + VECADDONS gameClients; + CBinaryAddonCache& addonCache = CServiceBroker::GetBinaryAddonCache(); + addonCache.GetAddons(gameClients, ADDON_GAMEDLL); + for (auto& gameClient : gameClients) + { + GameClientPtr gc(std::static_pointer_cast<CGameClient>(gameClient)); + if (gc->IsExtensionValid(extension)) + return true; + } + + // Check remote add-ons + gameClients.clear(); + if (CAddonMgr::GetInstance().GetInstallableAddons(gameClients, ADDON_GAMEDLL)) + { + for (auto& gameClient : gameClients) + { + GameClientPtr gc(std::static_pointer_cast<CGameClient>(gameClient)); + if (gc->IsExtensionValid(extension)) + return true; + } + } + + return false; +} + +std::set<std::string> CGameUtils::GetGameExtensions() +{ + using namespace ADDON; + + std::set<std::string> extensions; + + VECADDONS gameClients; + CBinaryAddonCache& addonCache = CServiceBroker::GetBinaryAddonCache(); + addonCache.GetAddons(gameClients, ADDON_GAMEDLL); + for (auto& gameClient : gameClients) + { + GameClientPtr gc(std::static_pointer_cast<CGameClient>(gameClient)); + extensions.insert(gc->GetExtensions().begin(), gc->GetExtensions().end()); + } + + // Check remote add-ons + gameClients.clear(); + if (CAddonMgr::GetInstance().GetInstallableAddons(gameClients, ADDON_GAMEDLL)) + { + for (auto& gameClient : gameClients) + { + GameClientPtr gc(std::static_pointer_cast<CGameClient>(gameClient)); + extensions.insert(gc->GetExtensions().begin(), gc->GetExtensions().end()); + } + } + + return extensions; +} + +bool CGameUtils::IsStandaloneGame(const ADDON::AddonPtr& addon) +{ + using namespace ADDON; + + switch (addon->Type()) + { + case ADDON_GAMEDLL: + { + return std::static_pointer_cast<GAME::CGameClient>(addon)->SupportsStandalone(); + } + case ADDON_SCRIPT: + { + return addon->IsType(ADDON_GAME); + } + default: + break; + } + + return false; +} diff --git a/xbmc/games/GameUtils.h b/xbmc/games/GameUtils.h new file mode 100644 index 0000000000..7c3ac9cd92 --- /dev/null +++ b/xbmc/games/GameUtils.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "GameTypes.h" +#include "addons/Addon.h" +#include "addons/IAddon.h" + +#include <set> +#include <string> + +class CFileItem; +class CURL; + +namespace GAME +{ + /*! + * \ingroup games + * \brief Game related utilities. + */ + class CGameUtils + { + public: + /*! + * \brief Select a game client, possibly via prompt, for the given game + * + * \param file The game being played + * + * \return A game client ready to be initialized for playback + */ + static GameClientPtr OpenGameClient(const CFileItem& file); + + /*! + * \brief Check if the file extension is supported by an add-on in + * a local or remote repository + * + * \param path The path of the game file + * + * \return true if the path's extension is supported by a known game client + */ + static bool HasGameExtension(const std::string& path); + + static std::set<std::string> GetGameExtensions(); + + /*! + * \brief Check if game script or game add-on can be launched directly + * + * \return true if the add-on can be launched, false otherwise + */ + static bool IsStandaloneGame(const ADDON::AddonPtr& addon); + + private: + static void GetGameClients(const CFileItem& file, GameClientVector& candidates, GameClientVector& installable, bool& bHasVfsGameClient); + static void GetGameClients(const ADDON::VECADDONS& addons, const CURL& translatedUrl, GameClientVector& candidates, bool& bHasVfsGameClient); + }; +} // namespace GAME diff --git a/xbmc/games/Makefile b/xbmc/games/Makefile index 14bb7c1a32..2aad479598 100644 --- a/xbmc/games/Makefile +++ b/xbmc/games/Makefile @@ -1,4 +1,5 @@ SRCS=GameSettings.cpp \ + GameUtils.cpp \ LIB=games.a diff --git a/xbmc/games/addons/CMakeLists.txt b/xbmc/games/addons/CMakeLists.txt new file mode 100644 index 0000000000..8a89ec63c6 --- /dev/null +++ b/xbmc/games/addons/CMakeLists.txt @@ -0,0 +1,18 @@ +set(SOURCES GameClient.cpp + GameClientInput.cpp + GameClientKeyboard.cpp + GameClientMouse.cpp + GameClientProperties.cpp + GameClientTiming.cpp + GameClientTranslator.cpp) + +set(HEADERS GameClient.h + GameClientCallbacks.h + GameClientInput.h + GameClientKeyboard.h + GameClientMouse.h + GameClientProperties.h + GameClientTiming.h + GameClientTranslator.h) + +core_add_library(gameaddons) diff --git a/xbmc/games/addons/GameClient.cpp b/xbmc/games/addons/GameClient.cpp new file mode 100644 index 0000000000..0c6f04e42d --- /dev/null +++ b/xbmc/games/addons/GameClient.cpp @@ -0,0 +1,918 @@ +/* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GameClient.h" +#include "GameClientCallbacks.h" +#include "GameClientInput.h" +#include "GameClientKeyboard.h" +#include "GameClientMouse.h" +#include "GameClientTranslator.h" +#include "addons/AddonManager.h" +#include "addons/BinaryAddonCache.h" +#include "cores/AudioEngine/Utils/AEChannelInfo.h" +#include "dialogs/GUIDialogOK.h" +#include "filesystem/Directory.h" +#include "filesystem/SpecialProtocol.h" +#include "games/addons/playback/GameClientRealtimePlayback.h" +#include "games/addons/playback/GameClientReversiblePlayback.h" +#include "games/controllers/Controller.h" +#include "games/ports/PortManager.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/WindowIDs.h" +#include "input/joysticks/DefaultJoystick.h" // for DEFAULT_CONTROLLER_ID +#include "input/joysticks/JoystickTypes.h" +#include "peripherals/Peripherals.h" +#include "profiles/ProfilesManager.h" +#include "settings/Settings.h" +#include "threads/SingleLock.h" +#include "utils/log.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "Application.h" +#include "FileItem.h" +#include "ServiceBroker.h" +#include "URL.h" + +#include <algorithm> +#include <cstring> +#include <iterator> +#include <utility> + +using namespace GAME; + +#define EXTENSION_SEPARATOR "|" +#define EXTENSION_WILDCARD "*" + +#define GAME_PROPERTY_EXTENSIONS "extensions" +#define GAME_PROPERTY_SUPPORTS_VFS "supports_vfs" +#define GAME_PROPERTY_SUPPORTS_STANDALONE "supports_standalone" +#define GAME_PROPERTY_SUPPORTS_KEYBOARD "supports_keyboard" +#define GAME_PROPERTY_SUPPORTS_MOUSE "supports_mouse" + +#define INPUT_SCAN_RATE 125 // Hz + +// --- NormalizeExtension ------------------------------------------------------ + +namespace +{ + /* + * \brief Convert to lower case and canonicalize with a leading "." + */ + std::string NormalizeExtension(const std::string& strExtension) + { + std::string ext = strExtension; + + if (!ext.empty() && ext != EXTENSION_WILDCARD) + { + StringUtils::ToLower(ext); + + if (ext[0] != '.') + ext.insert(0, "."); + } + + return ext; + } +} + +// --- CGameClient ------------------------------------------------------------- + +std::unique_ptr<CGameClient> CGameClient::FromExtension(ADDON::AddonProps props, const cp_extension_t* ext) +{ + using namespace ADDON; + + static const std::vector<std::string> properties = { + GAME_PROPERTY_EXTENSIONS, + GAME_PROPERTY_SUPPORTS_VFS, + GAME_PROPERTY_SUPPORTS_STANDALONE, + GAME_PROPERTY_SUPPORTS_KEYBOARD, + GAME_PROPERTY_SUPPORTS_MOUSE, + }; + + for (const auto& property : properties) + { + std::string strProperty = CAddonMgr::GetInstance().GetExtValue(ext->configuration, property.c_str()); + if (!strProperty.empty()) + props.extrainfo[property] = strProperty; + } + + return std::unique_ptr<CGameClient>(new CGameClient(std::move(props))); +} + +CGameClient::CGameClient(ADDON::AddonProps props) : + CAddonDll<DllGameClient, GameClient, game_client_properties>(std::move(props)), + m_apiVersion("0.0.0"), + m_libraryProps(this, m_pInfo), + m_bSupportsVFS(false), + m_bSupportsStandalone(false), + m_bSupportsKeyboard(false), + m_bSupportsMouse(false), + m_bSupportsAllExtensions(false), + m_bIsPlaying(false), + m_serializeSize(0), + m_audio(nullptr), + m_video(nullptr), + m_region(GAME_REGION_UNKNOWN) +{ + const ADDON::InfoMap& extraInfo = m_props.extrainfo; + ADDON::InfoMap::const_iterator it; + + it = extraInfo.find(GAME_PROPERTY_EXTENSIONS); + if (it != extraInfo.end()) + { + std::vector<std::string> extensions = StringUtils::Split(it->second, EXTENSION_SEPARATOR); + std::transform(extensions.begin(), extensions.end(), + std::inserter(m_extensions, m_extensions.begin()), NormalizeExtension); + + // Check for wildcard extension + if (m_extensions.find(EXTENSION_WILDCARD) != m_extensions.end()) + { + m_bSupportsAllExtensions = true; + m_extensions.clear(); + } + } + + it = extraInfo.find(GAME_PROPERTY_SUPPORTS_VFS); + if (it != extraInfo.end()) + m_bSupportsVFS = (it->second == "true"); + + it = extraInfo.find(GAME_PROPERTY_SUPPORTS_STANDALONE); + if (it != extraInfo.end()) + m_bSupportsStandalone = (it->second == "true"); + + it = extraInfo.find(GAME_PROPERTY_SUPPORTS_KEYBOARD); + if (it != extraInfo.end()) + m_bSupportsKeyboard = (it->second == "true"); + + it = extraInfo.find(GAME_PROPERTY_SUPPORTS_MOUSE); + if (it != extraInfo.end()) + m_bSupportsMouse = (it->second == "true"); + + ResetPlayback(); +} + +CGameClient::~CGameClient(void) +{ +} + +std::string CGameClient::LibPath() const +{ + // If the game client requires a proxy, load its DLL instead + if (m_pInfo->proxy_dll_count > 0) + return m_pInfo->proxy_dll_paths[0]; + + return CAddon::LibPath(); +} + +ADDON::AddonPtr CGameClient::GetRunningInstance() const +{ + using namespace ADDON; + + CBinaryAddonCache& addonCache = CServiceBroker::GetBinaryAddonCache(); + return addonCache.GetAddonInstance(ID(), Type()); +} + +bool CGameClient::SupportsPath() const +{ + return !m_extensions.empty() || m_bSupportsAllExtensions; +} + +bool CGameClient::IsExtensionValid(const std::string& strExtension) const +{ + if (strExtension.empty()) + return false; + + if (SupportsAllExtensions()) + return true; + + return m_extensions.find(NormalizeExtension(strExtension)) != m_extensions.end(); +} + +bool CGameClient::Initialize(void) +{ + using namespace XFILE; + + // Ensure user profile directory exists for add-on + if (!CDirectory::Exists(Profile())) + CDirectory::Create(Profile()); + + // Ensure directory exists for savestates + std::string savestatesDir = URIUtils::AddFileToFolder(CProfilesManager::GetInstance().GetSavestatesFolder(), ID()); + if (!CDirectory::Exists(savestatesDir)) + CDirectory::Create(savestatesDir); + + m_libraryProps.InitializeProperties(); + + if (Create() == ADDON_STATUS_OK) + { + LogAddonProperties(); + return true; + } + + return false; +} + +void CGameClient::Unload() +{ + Destroy(); +} + +bool CGameClient::OpenFile(const CFileItem& file, IGameAudioCallback* audio, IGameVideoCallback* video) +{ + if (audio == nullptr || video == nullptr) + return false; + + // Check if we should open in standalone mode + if (file.GetPath().empty()) + return OpenStandalone(audio, video); + + // Resolve special:// URLs + CURL translatedUrl(CSpecialProtocol::TranslatePath(file.GetPath())); + + // Remove file:// from URLs if add-on doesn't support VFS + if (!m_bSupportsVFS) + { + if (translatedUrl.GetProtocol() == "file") + translatedUrl.SetProtocol(""); + } + + std::string path = translatedUrl.Get(); + CLog::Log(LOGDEBUG, "GameClient: Loading %s", CURL::GetRedacted(path).c_str()); + + CSingleLock lock(m_critSection); + + if (!Initialized()) + return false; + + CloseFile(); + + GAME_ERROR error = GAME_ERROR_FAILED; + + try { LogError(error = m_pStruct->LoadGame(path.c_str()), "LoadGame()"); } + catch (...) { LogException("LoadGame()"); } + + if (error != GAME_ERROR_NO_ERROR) + { + NotifyError(error); + return false; + } + + if (!InitializeGameplay(file.GetPath(), audio, video)) + return false; + + return true; +} + +bool CGameClient::OpenStandalone(IGameAudioCallback* audio, IGameVideoCallback* video) +{ + CLog::Log(LOGDEBUG, "GameClient: Loading %s in standalone mode", ID().c_str()); + + CSingleLock lock(m_critSection); + + if (!Initialized()) + return false; + + CloseFile(); + + GAME_ERROR error = GAME_ERROR_FAILED; + + try { LogError(error = m_pStruct->LoadStandalone(), "LoadStandalone()"); } + catch (...) { LogException("LoadStandalone()"); } + + if (error != GAME_ERROR_NO_ERROR) + { + NotifyError(error); + return false; + } + + if (!InitializeGameplay(ID(), audio, video)) + return false; + + return true; +} + +bool CGameClient::InitializeGameplay(const std::string& gamePath, IGameAudioCallback* audio, IGameVideoCallback* video) +{ + if (LoadGameInfo() && NormalizeAudio(audio)) + { + m_bIsPlaying = true; + m_gamePath = gamePath; + m_serializeSize = GetSerializeSize(); + m_audio = audio; + m_video = video; + m_inputRateHandle = PERIPHERALS::g_peripherals.SetEventScanRate(INPUT_SCAN_RATE); + + if (m_bSupportsKeyboard) + OpenKeyboard(); + + if (m_bSupportsMouse) + OpenMouse(); + + // Start playback + CreatePlayback(); + + return true; + } + + return false; +} + +bool CGameClient::NormalizeAudio(IGameAudioCallback* audioCallback) +{ + unsigned int originalSampleRate = m_timing.GetSampleRate(); + + if (m_timing.NormalizeAudio(audioCallback)) + { + const bool bChanged = (originalSampleRate != m_timing.GetSampleRate()); + if (bChanged) + { + CLog::Log(LOGDEBUG, "GAME: Correcting audio and video by %f to avoid resampling", m_timing.GetCorrectionFactor()); + CLog::Log(LOGDEBUG, "GAME: Audio sample rate normalized to %u", m_timing.GetSampleRate()); + CLog::Log(LOGDEBUG, "GAME: Video frame rate scaled to %f", m_timing.GetFrameRate()); + } + else + { + CLog::Log(LOGDEBUG, "GAME: Audio sample rate is supported, no scaling or resampling needed"); + } + } + else + { + CLog::Log(LOGERROR, "GAME: Failed to normalize audio sample rate: exceeds %u%% difference", CGameClientTiming::MAX_CORRECTION_FACTOR_PERCENT); + return false; + } + + return true; +} + +bool CGameClient::LoadGameInfo() +{ + // Get information about system audio/video timings and geometry + // Can be called only after retro_load_game() + game_system_av_info av_info = { }; + + bool bSuccess = false; + try { bSuccess = LogError(m_pStruct->GetGameInfo(&av_info), "GetGameInfo()"); } + catch (...) { LogException("GetGameInfo()"); } + + if (!bSuccess) + return false; + + GAME_REGION region; + try { region = m_pStruct->GetRegion(); } + catch (...) { LogException("GetRegion()"); return false; } + + CLog::Log(LOGINFO, "GAME: ---------------------------------------"); + CLog::Log(LOGINFO, "GAME: Base Width: %u", av_info.geometry.base_width); + CLog::Log(LOGINFO, "GAME: Base Height: %u", av_info.geometry.base_height); + CLog::Log(LOGINFO, "GAME: Max Width: %u", av_info.geometry.max_width); + CLog::Log(LOGINFO, "GAME: Max Height: %u", av_info.geometry.max_height); + CLog::Log(LOGINFO, "GAME: Aspect Ratio: %f", av_info.geometry.aspect_ratio); + CLog::Log(LOGINFO, "GAME: FPS: %f", av_info.timing.fps); + CLog::Log(LOGINFO, "GAME: Sample Rate: %f", av_info.timing.sample_rate); + CLog::Log(LOGINFO, "GAME: Region: %s", CGameClientTranslator::TranslateRegion(region)); + CLog::Log(LOGINFO, "GAME: ---------------------------------------"); + + m_timing.SetFrameRate(av_info.timing.fps); + m_timing.SetSampleRate(av_info.timing.sample_rate); + m_region = region; + + return true; +} + +void CGameClient::NotifyError(GAME_ERROR error) +{ + std::string missingResource; + + if (error == GAME_ERROR_RESTRICTED) + missingResource = GetMissingResource(); + + if (!missingResource.empty()) + { + // Failed to play game + // This game requires the following add-on: %s + CGUIDialogOK::ShowAndGetInput(CVariant{ 35210 }, StringUtils::Format(g_localizeStrings.Get(35211).c_str(), missingResource.c_str())); + } + else + { + // Failed to play game + // The emulator "%s" had an internal error. + CGUIDialogOK::ShowAndGetInput(CVariant{ 35210 }, StringUtils::Format(g_localizeStrings.Get(35213).c_str(), Name().c_str())); + } +} + +std::string CGameClient::GetMissingResource() +{ + using namespace ADDON; + + std::string strAddonId; + + const ADDONDEPS& dependencies = GetDeps(); + for (ADDONDEPS::const_iterator it = dependencies.begin(); it != dependencies.end(); ++it) + { + const std::string& strDependencyId = it->first; + if (StringUtils::StartsWith(strDependencyId, "resource.games")) + { + AddonPtr addon; + const bool bInstalled = CAddonMgr::GetInstance().GetAddon(strDependencyId, addon); + if (!bInstalled) + { + strAddonId = strDependencyId; + break; + } + } + } + + return strAddonId; +} + +void CGameClient::CreatePlayback() +{ + bool bRequiresGameLoop = false; + + try { bRequiresGameLoop = m_pStruct->RequiresGameLoop(); } + catch (...) { LogException("RequiresGameLoop()"); } + + if (bRequiresGameLoop) + { + m_playback.reset(new CGameClientReversiblePlayback(this, m_timing.GetFrameRate(), m_serializeSize)); + } + else + { + ResetPlayback(); + } +} + +void CGameClient::ResetPlayback() +{ + m_playback.reset(new CGameClientRealtimePlayback); +} + +void CGameClient::Reset() +{ + ResetPlayback(); + + CSingleLock lock(m_critSection); + + if (m_bIsPlaying) + { + try { LogError(m_pStruct->Reset(), "Reset()"); } + catch (...) { LogException("Reset()"); } + + CreatePlayback(); + } +} + +void CGameClient::CloseFile() +{ + ResetPlayback(); + + CSingleLock lock(m_critSection); + + if (m_bIsPlaying) + { + try { LogError(m_pStruct->UnloadGame(), "UnloadGame()"); } + catch (...) { LogException("UnloadGame()"); } + } + + ClearPorts(); + + if (m_bSupportsKeyboard) + CloseKeyboard(); + + if (m_bSupportsMouse) + CloseMouse(); + + m_bIsPlaying = false; + m_gamePath.clear(); + m_serializeSize = 0; + if (m_inputRateHandle) + { + m_inputRateHandle->Release(); + m_inputRateHandle.reset(); + } + + m_audio = nullptr; + m_video = nullptr; + m_timing.Reset(); +} + +void CGameClient::RunFrame() +{ + CSingleLock lock(m_critSection); + + if (m_bIsPlaying) + { + try { LogError(m_pStruct->RunFrame(), "RunFrame()"); } + catch (...) { LogException("RunFrame()"); } + } +} + +bool CGameClient::OpenPixelStream(GAME_PIXEL_FORMAT format, unsigned int width, unsigned int height, GAME_VIDEO_ROTATION rotation) +{ + if (!m_video) + return false; + + AVPixelFormat pixelFormat = CGameClientTranslator::TranslatePixelFormat(format); + if (pixelFormat == AV_PIX_FMT_NONE) + { + CLog::Log(LOGERROR, "GAME: Unknown pixel format: %d", format); + return false; + } + + unsigned int orientation = 0; + switch (rotation) + { + case GAME_VIDEO_ROTATION_90: + orientation = 360 - 90; + break; + case GAME_VIDEO_ROTATION_180: + orientation = 360 - 180; + break; + case GAME_VIDEO_ROTATION_270: + orientation = 360 - 270; + break; + default: + break; + } + + return m_video->OpenPixelStream(pixelFormat, width, height, m_timing.GetFrameRate(), orientation); +} + +bool CGameClient::OpenVideoStream(GAME_VIDEO_CODEC codec) +{ + if (!m_video) + return false; + + AVCodecID videoCodec = CGameClientTranslator::TranslateVideoCodec(codec); + if (videoCodec == AV_CODEC_ID_NONE) + { + CLog::Log(LOGERROR, "GAME: Unknown video format: %d", codec); + return false; + } + + return m_video->OpenEncodedStream(videoCodec); +} + +bool CGameClient::OpenPCMStream(GAME_PCM_FORMAT format, const GAME_AUDIO_CHANNEL* channelMap) +{ + if (!m_audio || channelMap == nullptr) + return false; + + AEDataFormat pcmFormat = CGameClientTranslator::TranslatePCMFormat(format); + if (pcmFormat == AE_FMT_INVALID) + { + CLog::Log(LOGERROR, "GAME: Unknown PCM format: %d", format); + return false; + } + + CAEChannelInfo channelLayout; + for (const GAME_AUDIO_CHANNEL* channelPtr = channelMap; *channelPtr != GAME_CH_NULL; channelPtr++) + { + AEChannel channel = CGameClientTranslator::TranslateAudioChannel(*channelPtr); + if (channel == AE_CH_NULL) + { + CLog::Log(LOGERROR, "GAME: Unknown channel ID: %d", *channelPtr); + return false; + } + channelLayout += channel; + } + + return m_audio->OpenPCMStream(pcmFormat, m_timing.GetSampleRate(), channelLayout); +} + +bool CGameClient::OpenAudioStream(GAME_AUDIO_CODEC codec, const GAME_AUDIO_CHANNEL* channelMap) +{ + if (!m_audio) + return false; + + AVCodecID audioCodec = CGameClientTranslator::TranslateAudioCodec(codec); + if (audioCodec == AV_CODEC_ID_NONE) + { + CLog::Log(LOGERROR, "GAME: Unknown audio codec: %d", codec); + return false; + } + + CAEChannelInfo channelLayout; + for (const GAME_AUDIO_CHANNEL* channelPtr = channelMap; *channelPtr != GAME_CH_NULL; channelPtr++) + { + AEChannel channel = CGameClientTranslator::TranslateAudioChannel(*channelPtr); + if (channel == AE_CH_NULL) + { + CLog::Log(LOGERROR, "GAME: Unknown channel ID: %d", *channelPtr); + return false; + } + channelLayout += channel; + } + + return m_audio->OpenEncodedStream(audioCodec, m_timing.GetSampleRate(), channelLayout); +} + +void CGameClient::AddStreamData(GAME_STREAM_TYPE stream, const uint8_t* data, unsigned int size) +{ + switch (stream) + { + case GAME_STREAM_AUDIO: + { + if (m_audio) + m_audio->AddData(data, size); + break; + } + case GAME_STREAM_VIDEO: + { + if (m_video) + m_video->AddData(data, size); + break; + } + default: + break; + } +} + +void CGameClient::CloseStream(GAME_STREAM_TYPE stream) +{ + switch (stream) + { + case GAME_STREAM_AUDIO: + { + if (m_audio) + m_audio->CloseStream(); + break; + } + case GAME_STREAM_VIDEO: + { + if (m_video) + m_video->CloseStream(); + break; + } + default: + break; + } +} + +size_t CGameClient::GetSerializeSize() +{ + CSingleLock lock(m_critSection); + + size_t serializeSize = 0; + if (m_bIsPlaying) + { + try { serializeSize = m_pStruct->SerializeSize(); } + catch (...) { LogException("SerializeSize()"); } + } + + return serializeSize; +} + +bool CGameClient::Serialize(uint8_t* data, size_t size) +{ + if (data == nullptr || size == 0) + return false; + + CSingleLock lock(m_critSection); + + bool bSuccess = false; + if (m_bIsPlaying) + { + try { bSuccess = LogError(m_pStruct->Serialize(data, size), "Serialize()"); } + catch (...) { LogException("Serialize()"); } + } + + return bSuccess; +} + +bool CGameClient::Deserialize(const uint8_t* data, size_t size) +{ + if (data == nullptr || size == 0) + return false; + + CSingleLock lock(m_critSection); + + bool bSuccess = false; + if (m_bIsPlaying) + { + try { bSuccess = LogError(m_pStruct->Deserialize(data, size), "Deserialize()"); } + catch (...) { LogException("Deserialize()"); } + } + + return bSuccess; +} + +bool CGameClient::OpenPort(unsigned int port) +{ + // Fail if port is already open + if (m_ports.find(port) != m_ports.end()) + return false; + + ControllerVector controllers = GetControllers(); + if (!controllers.empty()) + { + //! @todo Choose controller + ControllerPtr& controller = controllers[0]; + + if (controller->LoadLayout()) + { + m_ports[port].reset(new CGameClientInput(this, port, controller, m_pStruct)); + + // If keyboard input is being captured by this add-on, force the port type to PERIPHERAL_JOYSTICK + PERIPHERALS::PeripheralType device = PERIPHERALS::PERIPHERAL_UNKNOWN; + if (m_bSupportsKeyboard) + device = PERIPHERALS::PERIPHERAL_JOYSTICK; + + CPortManager::GetInstance().OpenPort(m_ports[port].get(), port, device); + + UpdatePort(port, controller); + + return true; + } + } + + return false; +} + +void CGameClient::ClosePort(unsigned int port) +{ + // Can't close port if it doesn't exist + if (m_ports.find(port) == m_ports.end()) + return; + + CPortManager::GetInstance().ClosePort(m_ports[port].get()); + + m_ports.erase(port); + + UpdatePort(port, CController::EmptyPtr); +} + +void CGameClient::UpdatePort(unsigned int port, const ControllerPtr& controller) +{ + using namespace JOYSTICK; + + if (controller != CController::EmptyPtr) + { + std::string strId = controller->ID(); + + game_controller controllerStruct; + + controllerStruct.controller_id = strId.c_str(); + controllerStruct.digital_button_count = controller->Layout().FeatureCount(FEATURE_TYPE::SCALAR, INPUT_TYPE::DIGITAL); + controllerStruct.analog_button_count = controller->Layout().FeatureCount(FEATURE_TYPE::SCALAR, INPUT_TYPE::ANALOG); + controllerStruct.analog_stick_count = controller->Layout().FeatureCount(FEATURE_TYPE::ANALOG_STICK); + controllerStruct.accelerometer_count = controller->Layout().FeatureCount(FEATURE_TYPE::ACCELEROMETER); + controllerStruct.key_count = 0; //! @todo + controllerStruct.rel_pointer_count = controller->Layout().FeatureCount(FEATURE_TYPE::RELPOINTER); + controllerStruct.abs_pointer_count = 0; //! @todo + controllerStruct.motor_count = controller->Layout().FeatureCount(FEATURE_TYPE::MOTOR); + + try { m_pStruct->UpdatePort(port, true, &controllerStruct); } + catch (...) { LogException("UpdatePort()"); } + } + else + { + try { m_pStruct->UpdatePort(port, false, nullptr); } + catch (...) { LogException("UpdatePort()"); } + } +} + +bool CGameClient::AcceptsInput(void) const +{ + return g_application.IsAppFocused() && + g_windowManager.GetActiveWindowID() == WINDOW_FULLSCREEN_GAME; +} + +void CGameClient::ClearPorts(void) +{ + while (!m_ports.empty()) + ClosePort(m_ports.begin()->first); +} + +ControllerVector CGameClient::GetControllers(void) const +{ + using namespace ADDON; + + ControllerVector controllers; + + const ADDONDEPS& dependencies = GetDeps(); + for (ADDONDEPS::const_iterator it = dependencies.begin(); it != dependencies.end(); ++it) + { + AddonPtr addon; + if (CAddonMgr::GetInstance().GetAddon(it->first, addon, ADDON_GAME_CONTROLLER)) + { + ControllerPtr controller = std::dynamic_pointer_cast<CController>(addon); + if (controller) + controllers.push_back(controller); + } + } + + if (controllers.empty()) + { + // Use the default controller + AddonPtr addon; + if (CAddonMgr::GetInstance().GetAddon(DEFAULT_CONTROLLER_ID, addon, ADDON_GAME_CONTROLLER)) + controllers.push_back(std::static_pointer_cast<CController>(addon)); + } + + return controllers; +} + +bool CGameClient::ReceiveInputEvent(const game_input_event& event) +{ + bool bHandled = false; + + switch (event.type) + { + case GAME_INPUT_EVENT_MOTOR: + if (event.feature_name) + bHandled = SetRumble(event.port, event.feature_name, event.motor.magnitude); + break; + default: + break; + } + + return bHandled; +} + +bool CGameClient::SetRumble(unsigned int port, const std::string& feature, float magnitude) +{ + bool bHandled = false; + + if (m_ports.find(port) != m_ports.end()) + bHandled = m_ports[port]->SetRumble(feature, magnitude); + + return bHandled; +} + +void CGameClient::OpenKeyboard(void) +{ + m_keyboard.reset(new CGameClientKeyboard(this, m_pStruct)); +} + +void CGameClient::CloseKeyboard(void) +{ + m_keyboard.reset(); +} + +void CGameClient::OpenMouse(void) +{ + m_mouse.reset(new CGameClientMouse(this, m_pStruct)); + + std::string strId = m_mouse->ControllerID(); + + game_controller controllerStruct = { strId.c_str() }; + + try { m_pStruct->UpdatePort(GAME_INPUT_PORT_MOUSE, true, &controllerStruct); } + catch (...) { LogException("UpdatePort()"); } +} + +void CGameClient::CloseMouse(void) +{ + try { m_pStruct->UpdatePort(GAME_INPUT_PORT_MOUSE, false, nullptr); } + catch (...) { LogException("UpdatePort()"); } + + m_mouse.reset(); +} + +void CGameClient::LogAddonProperties(void) const +{ + CLog::Log(LOGINFO, "GAME: ------------------------------------"); + CLog::Log(LOGINFO, "GAME: Loaded DLL for %s", ID().c_str()); + CLog::Log(LOGINFO, "GAME: Client: %s at version %s", Name().c_str(), Version().asString().c_str()); + CLog::Log(LOGINFO, "GAME: Valid extensions: %s", StringUtils::Join(m_extensions, " ").c_str()); + CLog::Log(LOGINFO, "GAME: Supports VFS: %s", m_bSupportsVFS ? "yes" : "no"); + CLog::Log(LOGINFO, "GAME: Supports standalone execution: %s", m_bSupportsStandalone ? "yes" : "no"); + CLog::Log(LOGINFO, "GAME: Supports keyboard: %s", m_bSupportsKeyboard ? "yes" : "no"); + CLog::Log(LOGINFO, "GAME: Supports mouse: %s", m_bSupportsMouse ? "yes" : "no"); + CLog::Log(LOGINFO, "GAME: ------------------------------------"); +} + +bool CGameClient::LogError(GAME_ERROR error, const char* strMethod) const +{ + if (error != GAME_ERROR_NO_ERROR) + { + CLog::Log(LOGERROR, "GAME - %s - addon '%s' returned an error: %s", + strMethod, ID().c_str(), CGameClientTranslator::ToString(error)); + return false; + } + return true; +} + +void CGameClient::LogException(const char* strFunctionName) const +{ + CLog::Log(LOGERROR, "GAME: exception caught while trying to call '%s' on add-on %s", + strFunctionName, ID().c_str()); + CLog::Log(LOGERROR, "Please contact the developer of this add-on: %s", Author().c_str()); +} diff --git a/xbmc/games/addons/GameClient.h b/xbmc/games/addons/GameClient.h new file mode 100644 index 0000000000..a88b644030 --- /dev/null +++ b/xbmc/games/addons/GameClient.h @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "GameClientProperties.h" +#include "GameClientTiming.h" +#include "addons/AddonDll.h" +#include "addons/DllGameClient.h" +#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h" +#include "games/controllers/ControllerTypes.h" +#include "games/GameTypes.h" +#include "peripherals/EventScanRate.h" +#include "threads/CriticalSection.h" + +#include <atomic> +#include <set> +#include <stdint.h> +#include <string> +#include <vector> + +class CFileItem; + +namespace GAME +{ + +class CGameClientInput; +class CGameClientKeyboard; +class CGameClientMouse; +class IGameAudioCallback; +class IGameClientPlayback; +class IGameVideoCallback; + +// --- CGameClient ------------------------------------------------------------- + +/*! + * \ingroup games + * \brief Interface between Kodi and Game add-ons. + */ +class CGameClient : public ADDON::CAddonDll<DllGameClient, GameClient, game_client_properties> +{ +public: + static std::unique_ptr<CGameClient> FromExtension(ADDON::AddonProps props, const cp_extension_t* ext); + + CGameClient(ADDON::AddonProps props); + + virtual ~CGameClient(void); + + // Implementation of IAddon via CAddonDll + virtual std::string LibPath() const override; + virtual ADDON::AddonPtr GetRunningInstance() const override; + + // Query properties of the game client + bool SupportsStandalone() const { return m_bSupportsStandalone; } + bool SupportsPath() const; + bool SupportsVFS() const { return m_bSupportsVFS; } + const std::set<std::string>& GetExtensions() const { return m_extensions; } + bool SupportsAllExtensions() const { return m_bSupportsAllExtensions; } + bool IsExtensionValid(const std::string& strExtension) const; + + // Start/stop gameplay + bool Initialize(void); + void Unload(); + bool OpenFile(const CFileItem& file, IGameAudioCallback* audio, IGameVideoCallback* video); + void Reset(); + void CloseFile(); + const std::string& GetGamePath() const { return m_gamePath; } + + // Playback control + bool IsPlaying() const { return m_bIsPlaying; } + IGameClientPlayback* GetPlayback() { return m_playback.get(); } + const CGameClientTiming& Timing() const { return m_timing; } + void RunFrame(); + + // Audio/video callbacks + bool OpenPixelStream(GAME_PIXEL_FORMAT format, unsigned int width, unsigned int height, GAME_VIDEO_ROTATION rotation); + bool OpenVideoStream(GAME_VIDEO_CODEC codec); + bool OpenPCMStream(GAME_PCM_FORMAT format, const GAME_AUDIO_CHANNEL* channelMap); + bool OpenAudioStream(GAME_AUDIO_CODEC codec, const GAME_AUDIO_CHANNEL* channelMap); + void AddStreamData(GAME_STREAM_TYPE stream, const uint8_t* data, unsigned int size); + void CloseStream(GAME_STREAM_TYPE stream); + + // Access memory + size_t SerializeSize() const { return m_serializeSize; } + bool Serialize(uint8_t* data, size_t size); + bool Deserialize(const uint8_t* data, size_t size); + + // Input callbacks + bool OpenPort(unsigned int port); + void ClosePort(unsigned int port); + bool ReceiveInputEvent(const game_input_event& eventStruct); + + // Input functions + bool AcceptsInput(void) const; + +private: + // Private gameplay functions + bool OpenStandalone(IGameAudioCallback* audio, IGameVideoCallback* video); + bool InitializeGameplay(const std::string& gamePath, IGameAudioCallback* audio, IGameVideoCallback* video); + bool LoadGameInfo(); + bool NormalizeAudio(IGameAudioCallback* audioCallback); + void NotifyError(GAME_ERROR error); + std::string GetMissingResource(); + void CreatePlayback(); + void ResetPlayback(); + + // Private input functions + void UpdatePort(unsigned int port, const ControllerPtr& controller); + void ClearPorts(void); + bool SetRumble(unsigned int port, const std::string& feature, float magnitude); + void OpenKeyboard(void); + void CloseKeyboard(void); + void OpenMouse(void); + void CloseMouse(void); + ControllerVector GetControllers(void) const; + + // Private memory stream functions + size_t GetSerializeSize(); + + // Helper functions + void LogAddonProperties(void) const; + bool LogError(GAME_ERROR error, const char* strMethod) const; + void LogException(const char* strFunctionName) const; + + // Add-on properties + ADDON::AddonVersion m_apiVersion; + CGameClientProperties m_libraryProps; // Properties to pass to the DLL + + // Game API xml parameters + bool m_bSupportsVFS; + bool m_bSupportsStandalone; + bool m_bSupportsKeyboard; + bool m_bSupportsMouse; + std::set<std::string> m_extensions; + bool m_bSupportsAllExtensions; + //GamePlatforms m_platforms; + + // Properties of the current playing file + std::atomic_bool m_bIsPlaying; // True between OpenFile() and CloseFile() + std::string m_gamePath; + size_t m_serializeSize; + IGameAudioCallback* m_audio; // The audio callback passed to OpenFile() + IGameVideoCallback* m_video; // The video callback passed to OpenFile() + CGameClientTiming m_timing; // Class to scale playback to avoid resampling audio + PERIPHERALS::EventRateHandle m_inputRateHandle; // Handle while keeping the input sampling rate at the frame rate + std::unique_ptr<IGameClientPlayback> m_playback; // Interface to control playback + GAME_REGION m_region; // Region of the loaded game + + // Input + std::map<int, std::unique_ptr<CGameClientInput>> m_ports; + std::unique_ptr<CGameClientKeyboard> m_keyboard; + std::unique_ptr<CGameClientMouse> m_mouse; + + CCriticalSection m_critSection; +}; + +} // namespace GAME diff --git a/xbmc/games/addons/GameClientCallbacks.h b/xbmc/games/addons/GameClientCallbacks.h new file mode 100644 index 0000000000..316a460082 --- /dev/null +++ b/xbmc/games/addons/GameClientCallbacks.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "cores/AudioEngine/Utils/AEChannelData.h" + +#include "libavcodec/avcodec.h" +#include "libavutil/pixfmt.h" + +#include <stdint.h> + +class CAEChannelInfo; + +namespace GAME +{ + class IGameAudioCallback + { + public: + virtual ~IGameAudioCallback() { } + + virtual unsigned int NormalizeSamplerate(unsigned int samplerate) const = 0; + virtual bool OpenPCMStream(AEDataFormat format, unsigned int samplerate, const CAEChannelInfo& channelLayout) = 0; + virtual bool OpenEncodedStream(AVCodecID codec, unsigned int samplerate, const CAEChannelInfo& channelLayout) = 0; + virtual void AddData(const uint8_t* data, unsigned int size) = 0; + virtual void CloseStream() = 0; + }; + + class IGameVideoCallback + { + public: + virtual ~IGameVideoCallback() { } + + virtual bool OpenPixelStream(AVPixelFormat pixfmt, unsigned int width, unsigned int height, double framerate, unsigned int orientationDeg) = 0; + virtual bool OpenEncodedStream(AVCodecID codec) = 0; + virtual void AddData(const uint8_t* data, unsigned int size) = 0; + virtual void CloseStream() = 0; + }; +} diff --git a/xbmc/games/addons/GameClientInput.cpp b/xbmc/games/addons/GameClientInput.cpp new file mode 100644 index 0000000000..7c7b44ebe1 --- /dev/null +++ b/xbmc/games/addons/GameClientInput.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2015-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GameClientInput.h" +#include "GameClient.h" +#include "games/controllers/Controller.h" +#include "input/joysticks/IInputReceiver.h" + +#include <algorithm> +#include <assert.h> + +using namespace GAME; + +CGameClientInput::CGameClientInput(CGameClient* gameClient, int port, const ControllerPtr& controller, const GameClient *dllStruct) : + m_gameClient(gameClient), + m_port(port), + m_controller(controller), + m_dllStruct(dllStruct) +{ + assert(m_gameClient != NULL); + assert(m_controller.get() != NULL); +} + +std::string CGameClientInput::ControllerID(void) const +{ + return m_controller->ID(); +} + +bool CGameClientInput::HasFeature(const std::string& feature) const +{ + try + { + return m_dllStruct->HasFeature(m_controller->ID().c_str(), feature.c_str()); + } + catch (...) + { + CLog::Log(LOGERROR, "GAME: %s: exception caught in HasFeature()", m_gameClient->ID().c_str()); + } + + return false; +} + +bool CGameClientInput::AcceptsInput(void) +{ + return m_gameClient->AcceptsInput(); +} + +JOYSTICK::INPUT_TYPE CGameClientInput::GetInputType(const std::string& feature) const +{ + const std::vector<CControllerFeature>& features = m_controller->Layout().Features(); + + for (std::vector<CControllerFeature>::const_iterator it = features.begin(); it != features.end(); ++it) + { + if (feature == it->Name()) + return it->InputType(); + } + + return JOYSTICK::INPUT_TYPE::UNKNOWN; +} + +bool CGameClientInput::OnButtonPress(const std::string& feature, bool bPressed) +{ + bool bHandled = false; + + game_input_event event; + + std::string controllerId = m_controller->ID(); + + event.type = GAME_INPUT_EVENT_DIGITAL_BUTTON; + event.port = m_port; + event.controller_id = controllerId.c_str(); + event.feature_name = feature.c_str(); + event.digital_button.pressed = bPressed; + + try + { + bHandled = m_dllStruct->InputEvent(&event); + } + catch (...) + { + CLog::Log(LOGERROR, "GAME: %s: exception caught in InputEvent()", m_gameClient->ID().c_str()); + } + + return bHandled; +} + +bool CGameClientInput::OnButtonMotion(const std::string& feature, float magnitude) +{ + bool bHandled = false; + + game_input_event event; + + std::string controllerId = m_controller->ID(); + + event.type = GAME_INPUT_EVENT_ANALOG_BUTTON; + event.port = m_port; + event.controller_id = controllerId.c_str(); + event.feature_name = feature.c_str(); + event.analog_button.magnitude = magnitude; + + try + { + bHandled = m_dllStruct->InputEvent(&event); + } + catch (...) + { + CLog::Log(LOGERROR, "GAME: %s: exception caught in InputEvent()", m_gameClient->ID().c_str()); + } + + return bHandled; +} + +bool CGameClientInput::OnAnalogStickMotion(const std::string& feature, float x, float y, unsigned int motionTimeMs /* = 0 */) +{ + bool bHandled = false; + + game_input_event event; + + std::string controllerId = m_controller->ID(); + + event.type = GAME_INPUT_EVENT_ANALOG_STICK; + event.port = m_port; + event.controller_id = controllerId.c_str(); + event.feature_name = feature.c_str(); + event.analog_stick.x = x; + event.analog_stick.y = y; + + try + { + bHandled = m_dllStruct->InputEvent(&event); + } + catch (...) + { + CLog::Log(LOGERROR, "GAME: %s: exception caught in InputEvent()", m_gameClient->ID().c_str()); + } + + return bHandled; +} + +bool CGameClientInput::OnAccelerometerMotion(const std::string& feature, float x, float y, float z) +{ + bool bHandled = false; + + game_input_event event; + + std::string controllerId = m_controller->ID(); + + event.type = GAME_INPUT_EVENT_ACCELEROMETER; + event.port = m_port; + event.controller_id = controllerId.c_str(); + event.feature_name = feature.c_str(); + event.accelerometer.x = x; + event.accelerometer.y = y; + event.accelerometer.z = z; + + try + { + bHandled = m_dllStruct->InputEvent(&event); + } + catch (...) + { + CLog::Log(LOGERROR, "GAME: %s: exception caught in InputEvent()", m_gameClient->ID().c_str()); + } + + return bHandled; +} + +bool CGameClientInput::SetRumble(const std::string& feature, float magnitude) +{ + bool bHandled = false; + + if (InputReceiver()) + bHandled = InputReceiver()->SetRumbleState(feature, magnitude); + + return bHandled; +} diff --git a/xbmc/games/addons/GameClientInput.h b/xbmc/games/addons/GameClientInput.h new file mode 100644 index 0000000000..75bfd5668b --- /dev/null +++ b/xbmc/games/addons/GameClientInput.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "games/controllers/ControllerTypes.h" +#include "input/joysticks/IInputHandler.h" + +struct GameClient; + +namespace GAME +{ + class CGameClient; + + /*! + * \ingroup games + * \brief Handles game controller events for games. + * + * Listens to game controller events and forwards them to the games (as game_input_event). + */ + class CGameClientInput : public JOYSTICK::IInputHandler + { + public: + /*! + * \brief Constructor. + * \param addon The game client implementation. + * \param port The port this game controller is associated with. + * \param controller The game controller which is used (for controller mapping). + * \param dllStruct The emulator or game to which the events are sent. + */ + CGameClientInput(CGameClient* addon, int port, const ControllerPtr& controller, const GameClient* dllStruct); + + // Implementation of IInputHandler + virtual std::string ControllerID(void) const override; + virtual bool HasFeature(const std::string& feature) const override; + virtual bool AcceptsInput(void) override; + virtual JOYSTICK::INPUT_TYPE GetInputType(const std::string& feature) const override; + virtual bool OnButtonPress(const std::string& feature, bool bPressed) override; + virtual void OnButtonHold(const std::string& feature, unsigned int holdTimeMs) override { } + virtual bool OnButtonMotion(const std::string& feature, float magnitude) override; + virtual bool OnAnalogStickMotion(const std::string& feature, float x, float y, unsigned int motionTimeMs = 0) override; + virtual bool OnAccelerometerMotion(const std::string& feature, float x, float y, float z) override; + + bool SetRumble(const std::string& feature, float magnitude); + + private: + const CGameClient* const m_gameClient; + const int m_port; + const ControllerPtr m_controller; + const GameClient* const m_dllStruct; + }; +} diff --git a/xbmc/games/addons/GameClientKeyboard.cpp b/xbmc/games/addons/GameClientKeyboard.cpp new file mode 100644 index 0000000000..157e202183 --- /dev/null +++ b/xbmc/games/addons/GameClientKeyboard.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GameClientKeyboard.h" +#include "GameClient.h" +#include "GameClientTranslator.h" +#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h" +#include "input/InputManager.h" +#include "input/Key.h" +#include "utils/log.h" + +using namespace GAME; + +#define BUTTON_INDEX_MASK 0x01ff + +CGameClientKeyboard::CGameClientKeyboard(const CGameClient* gameClient, const GameClient* dllStruct) : + m_gameClient(gameClient), + m_dllStruct(dllStruct) +{ + CInputManager::GetInstance().RegisterKeyboardHandler(this); +} + +CGameClientKeyboard::~CGameClientKeyboard() +{ + CInputManager::GetInstance().UnregisterKeyboardHandler(this); +} + +bool CGameClientKeyboard::OnKeyPress(const CKey& key) +{ + // Only allow activated input in fullscreen game + if (!m_gameClient->AcceptsInput()) + { + CLog::Log(LOGDEBUG, "GAME: key press ignored, not in fullscreen game"); + return false; + } + + bool bHandled = false; + + game_input_event event; + + event.type = GAME_INPUT_EVENT_KEY; + event.port = GAME_INPUT_PORT_KEYBOARD; + event.controller_id = ""; //! @todo + event.feature_name = ""; //! @todo + event.key.pressed = true; + event.key.character = static_cast<XBMCVKey>(key.GetButtonCode() & BUTTON_INDEX_MASK); + event.key.modifiers = CGameClientTranslator::GetModifiers(static_cast<CKey::Modifier>(key.GetModifiers())); + + if (event.key.character != 0) + { + try + { + bHandled = m_dllStruct->InputEvent(&event); + } + catch (...) + { + CLog::Log(LOGERROR, "GAME: %s: exception caught in InputEvent()", m_gameClient->ID().c_str()); + } + } + + return bHandled; +} + +void CGameClientKeyboard::OnKeyRelease(const CKey& key) +{ + game_input_event event; + + event.type = GAME_INPUT_EVENT_KEY; + event.port = GAME_INPUT_PORT_KEYBOARD; + event.controller_id = ""; //! @todo + event.feature_name = ""; //! @todo + event.key.pressed = false; + event.key.character = static_cast<XBMCVKey>(key.GetButtonCode() & BUTTON_INDEX_MASK); + event.key.modifiers = CGameClientTranslator::GetModifiers(static_cast<CKey::Modifier>(key.GetModifiers())); + + if (event.key.character != 0) + { + try + { + m_dllStruct->InputEvent(&event); + } + catch (...) + { + CLog::Log(LOGERROR, "GAME: %s: exception caught in InputEvent()", m_gameClient->ID().c_str()); + } + } +} diff --git a/xbmc/games/addons/GameClientKeyboard.h b/xbmc/games/addons/GameClientKeyboard.h new file mode 100644 index 0000000000..d565c1e7fd --- /dev/null +++ b/xbmc/games/addons/GameClientKeyboard.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2015-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "input/keyboard/IKeyboardHandler.h" + +struct GameClient; + +namespace GAME +{ + class CGameClient; + + /*! + * \ingroup games + * \brief Handles keyboard events for games. + * + * Listens to keyboard events and forwards them to the games (as game_input_event). + */ + class CGameClientKeyboard : public KEYBOARD::IKeyboardHandler + { + public: + /*! + * \brief Constructor registers for keyboard events at CInputManager. + * \param gameClient The game client implementation. + * \param dllStruct The emulator or game to which the events are sent. + */ + CGameClientKeyboard(const CGameClient* gameClient, const GameClient* dllStruct); + + /*! + * \brief Destructor unregisters from keyboard events from CInputManager. + */ + ~CGameClientKeyboard(); + + // implementation of IKeyboardHandler + virtual bool OnKeyPress(const CKey& key) override; + virtual void OnKeyRelease(const CKey& key) override; + + private: + // Construction parameters + const CGameClient* const m_gameClient; + const GameClient* const m_dllStruct; + }; +} diff --git a/xbmc/games/addons/GameClientMouse.cpp b/xbmc/games/addons/GameClientMouse.cpp new file mode 100644 index 0000000000..20f7dd9fbe --- /dev/null +++ b/xbmc/games/addons/GameClientMouse.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2015-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GameClientMouse.h" +#include "GameClient.h" +#include "GameClientTranslator.h" +#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h" +#include "input/InputManager.h" +#include "input/Key.h" +#include "utils/log.h" + +using namespace GAME; + +CGameClientMouse::CGameClientMouse(const CGameClient* gameClient, const GameClient* dllStruct) : + m_gameClient(gameClient), + m_dllStruct(dllStruct), + m_controllerId(CInputManager::GetInstance().RegisterMouseHandler(this)) +{ +} + +CGameClientMouse::~CGameClientMouse() +{ + CInputManager::GetInstance().UnregisterMouseHandler(this); +} + +std::string CGameClientMouse::ControllerID(void) const +{ + return m_controllerId; +} + +bool CGameClientMouse::OnMotion(const std::string& relpointer, int dx, int dy) +{ + // Only allow activated input in fullscreen game + if (!m_gameClient->AcceptsInput()) + { + return false; + } + + bool bHandled = false; + + game_input_event event; + + event.type = GAME_INPUT_EVENT_RELATIVE_POINTER; + event.port = GAME_INPUT_PORT_MOUSE; + event.controller_id = m_controllerId.c_str(); + event.feature_name = relpointer.c_str(); + event.rel_pointer.x = dx; + event.rel_pointer.y = dy; + + try + { + bHandled = m_dllStruct->InputEvent(&event); + } + catch (...) + { + CLog::Log(LOGERROR, "GAME: %s: exception caught in InputEvent()", m_gameClient->ID().c_str()); + } + + return bHandled; +} + +bool CGameClientMouse::OnButtonPress(const std::string& button) +{ + // Only allow activated input in fullscreen game + if (!m_gameClient->AcceptsInput()) + { + return false; + } + + bool bHandled = false; + + game_input_event event; + + event.type = GAME_INPUT_EVENT_DIGITAL_BUTTON; + event.port = GAME_INPUT_PORT_MOUSE; + event.controller_id = m_controllerId.c_str(); + event.feature_name = button.c_str(); + event.digital_button.pressed = true; + + try + { + bHandled = m_dllStruct->InputEvent(&event); + } + catch (...) + { + CLog::Log(LOGERROR, "GAME: %s: exception caught in InputEvent()", m_gameClient->ID().c_str()); + } + + return bHandled; +} + +void CGameClientMouse::OnButtonRelease(const std::string& button) +{ + game_input_event event; + + event.type = GAME_INPUT_EVENT_DIGITAL_BUTTON; + event.port = GAME_INPUT_PORT_MOUSE; + event.controller_id = m_controllerId.c_str(); + event.feature_name = button.c_str(); + event.digital_button.pressed = false; + + try + { + m_dllStruct->InputEvent(&event); + } + catch (...) + { + CLog::Log(LOGERROR, "GAME: %s: exception caught in InputEvent()", m_gameClient->ID().c_str()); + } +} diff --git a/xbmc/games/addons/GameClientMouse.h b/xbmc/games/addons/GameClientMouse.h new file mode 100644 index 0000000000..41d0c4efa5 --- /dev/null +++ b/xbmc/games/addons/GameClientMouse.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "input/mouse/IMouseInputHandler.h" + +struct GameClient; + +namespace GAME +{ + class CGameClient; + + /*! + * \ingroup games + * \brief Handles mouse events for games. + * + * Listens to mouse events and forwards them to the games (as game_input_event). + */ + class CGameClientMouse : public MOUSE::IMouseInputHandler + { + public: + /*! + * \brief Constructor registers for mouse events at CInputManager. + * \param gameClient The game client implementation. + * \param dllStruct The emulator or game to which the events are sent. + */ + CGameClientMouse(const CGameClient* gameClient, const GameClient* dllStruct); + + /*! + * \brief Destructor unregisters from mouse events from CInputManager. + */ + ~CGameClientMouse(); + + // implementation of IMouseInputHandler + virtual std::string ControllerID(void) const override; + virtual bool OnMotion(const std::string& relpointer, int dx, int dy) override; + virtual bool OnButtonPress(const std::string& button) override; + virtual void OnButtonRelease(const std::string& button) override; + + private: + // Construction parameters + const CGameClient* const m_gameClient; + const GameClient* const m_dllStruct; + const std::string m_controllerId; + }; +} diff --git a/xbmc/games/addons/GameClientProperties.cpp b/xbmc/games/addons/GameClientProperties.cpp new file mode 100644 index 0000000000..c2a8f63753 --- /dev/null +++ b/xbmc/games/addons/GameClientProperties.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GameClientProperties.h" +#include "GameClient.h" +#include "addons/IAddon.h" +#include "addons/AddonManager.h" +#include "addons/GameResource.h" +#include "dialogs/GUIDialogYesNo.h" +#include "filesystem/Directory.h" +#include "filesystem/SpecialProtocol.h" +#include "settings/Settings.h" +#include "utils/log.h" +#include "utils/Variant.h" + +#include <cstring> + +using namespace ADDON; +using namespace GAME; +using namespace XFILE; + +#define GAME_CLIENT_RESOURCES_DIRECTORY "resources" + +CGameClientProperties::CGameClientProperties(const CGameClient* parent, game_client_properties*& props) + : m_parent(parent), + m_properties() +{ + // Allow the caller to access the property structure directly + props = &m_properties; +} + +void CGameClientProperties::ReleaseResources(void) +{ + for (std::vector<char*>::const_iterator it = m_proxyDllPaths.begin(); it != m_proxyDllPaths.end(); ++it) + delete[] *it; + m_proxyDllPaths.clear(); + + for (std::vector<char*>::const_iterator it = m_resourceDirectories.begin(); it != m_resourceDirectories.end(); ++it) + delete[] *it; + m_resourceDirectories.clear(); + + for (std::vector<char*>::const_iterator it = m_extensions.begin(); it != m_extensions.end(); ++it) + delete[] *it; + m_extensions.clear(); +} + +void CGameClientProperties::InitializeProperties(void) +{ + ReleaseResources(); + + m_properties.game_client_dll_path = GetLibraryPath(); + m_properties.proxy_dll_paths = GetProxyDllPaths(); + m_properties.proxy_dll_count = GetProxyDllCount(); + m_properties.resource_directories = GetResourceDirectories(); + m_properties.resource_directory_count = GetResourceDirectoryCount(); + m_properties.profile_directory = GetProfileDirectory(); + m_properties.supports_vfs = m_parent->SupportsVFS(); + m_properties.extensions = GetExtensions(); + m_properties.extension_count = GetExtensionCount(); +} + +const char* CGameClientProperties::GetLibraryPath(void) +{ + if (m_strLibraryPath.empty()) + { + // Get the parent add-on's real path + std::string strLibPath = m_parent->CAddon::LibPath(); + m_strLibraryPath = CSpecialProtocol::TranslatePath(strLibPath); + } + return m_strLibraryPath.c_str(); +} + +const char** CGameClientProperties::GetProxyDllPaths(void) +{ + if (m_proxyDllPaths.empty()) + { + // Add all game client dependencies + //! @todo Compare helper version with required dependency + const ADDONDEPS& dependencies = m_parent->GetDeps(); + for (ADDONDEPS::const_iterator it = dependencies.begin(); it != dependencies.end(); ++it) + { + const std::string& strAddonId = it->first; + AddonPtr addon; + if (CAddonMgr::GetInstance().GetAddon(strAddonId, addon, ADDON_GAMEDLL, false)) + { + // If add-on is disabled, ask the user to enable it + if (CAddonMgr::GetInstance().IsAddonDisabled(addon->ID())) + { + // Failed to play game + // This game depends on a disabled add-on. Would you like to enable it? + if (CGUIDialogYesNo::ShowAndGetInput(CVariant{ 35210 }, CVariant{ 35215 })) + CAddonMgr::GetInstance().EnableAddon(addon->ID()); + else + addon.reset(); + } + } + + if (addon) + AddProxyDll(std::static_pointer_cast<CGameClient>(addon)); + } + } + + if (!m_proxyDllPaths.empty()) + return const_cast<const char**>(m_proxyDllPaths.data()); + + return nullptr; +} + +const char** CGameClientProperties::GetResourceDirectories(void) +{ + if (m_resourceDirectories.empty()) + { + // Add all other game resources + const ADDONDEPS& dependencies = m_parent->GetDeps(); + for (ADDONDEPS::const_iterator it = dependencies.begin(); it != dependencies.end(); ++it) + { + const std::string& strAddonId = it->first; + AddonPtr addon; + if (CAddonMgr::GetInstance().GetAddon(strAddonId, addon, ADDON_RESOURCE_GAMES)) + { + std::shared_ptr<CGameResource> resource = std::static_pointer_cast<CGameResource>(addon); + + std::string resourcePath = resource->GetFullPath(""); + + char* resourceDir = new char[resourcePath.length() + 1]; + std::strcpy(resourceDir, resourcePath.c_str()); + m_resourceDirectories.push_back(resourceDir); + } + } + + // Add resource directories for profile and path + std::string addonProfile = CSpecialProtocol::TranslatePath(m_parent->Profile()); + std::string addonPath = m_parent->Path(); + + addonProfile = URIUtils::AddFileToFolder(addonProfile, GAME_CLIENT_RESOURCES_DIRECTORY); + addonPath = URIUtils::AddFileToFolder(addonPath, GAME_CLIENT_RESOURCES_DIRECTORY); + + if (!CDirectory::Exists(addonProfile)) + { + CLog::Log(LOGDEBUG, "Creating resource directory: %s", addonProfile.c_str()); + CDirectory::Create(addonProfile); + } + + char* addonProfileDir = new char[addonProfile.length() + 1]; + std::strcpy(addonProfileDir, addonProfile.c_str()); + m_resourceDirectories.push_back(addonProfileDir); + + char* addonPathDir = new char[addonPath.length() + 1]; + std::strcpy(addonPathDir, addonPath.c_str()); + m_resourceDirectories.push_back(addonPathDir); + } + + if (!m_resourceDirectories.empty()) + return const_cast<const char**>(m_resourceDirectories.data()); + + return nullptr; +} + +const char* CGameClientProperties::GetProfileDirectory(void) +{ + if (m_strProfileDirectory.empty()) + m_strProfileDirectory = CSpecialProtocol::TranslatePath(m_parent->Profile()); + + return m_strProfileDirectory.c_str(); +} + +const char** CGameClientProperties::GetExtensions(void) +{ + for (auto& extension : m_parent->GetExtensions()) + { + char* ext = new char[extension.length() + 1]; + std::strcpy(ext, extension.c_str()); + m_extensions.push_back(ext); + } + + return !m_extensions.empty() ? const_cast<const char**>(m_extensions.data()) : nullptr; +} + +void CGameClientProperties::AddProxyDll(const GameClientPtr& gameClient) +{ + // Get the add-on's real path + std::string strLibPath = gameClient->CAddon::LibPath(); + + // Ignore add-on if it is already added + if (!HasProxyDll(strLibPath)) + { + char* libPath = new char[strLibPath.length() + 1]; + std::strcpy(libPath, strLibPath.c_str()); + m_proxyDllPaths.push_back(libPath); + } +} + +bool CGameClientProperties::HasProxyDll(const std::string& strLibPath) const +{ + for (std::vector<char*>::const_iterator it = m_proxyDllPaths.begin(); it != m_proxyDllPaths.end(); ++it) + { + if (strLibPath == *it) + return true; + } + return false; +} diff --git a/xbmc/games/addons/GameClientProperties.h b/xbmc/games/addons/GameClientProperties.h new file mode 100644 index 0000000000..26a8aa5dd0 --- /dev/null +++ b/xbmc/games/addons/GameClientProperties.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h" +#include "games/GameTypes.h" + +#include <string> +#include <vector> + +struct game_client_properties; + +namespace GAME +{ + +class CGameClient; + +/** + * \ingroup games + * \brief C++ wrapper for game client properties declared in kodi_game_types.h + */ +class CGameClientProperties +{ +public: + CGameClientProperties(const CGameClient* parent, game_client_properties*& props); + ~CGameClientProperties(void) { ReleaseResources(); } + + void InitializeProperties(void); + +private: + // Release mutable resources + void ReleaseResources(void); + + // Equal to parent's real library path + const char* GetLibraryPath(void); + + // List of proxy DLLs needed to load the game client + const char** GetProxyDllPaths(void); + + // Number of proxy DLLs needed to load the game client + unsigned int GetProxyDllCount(void) const { return m_proxyDllPaths.size(); } + + // Paths to game resources + const char** GetResourceDirectories(void); + + // Number of resource directories + unsigned int GetResourceDirectoryCount(void) const { return m_resourceDirectories.size(); } + + // Equal to special://profile/addon_data/<parent's id> + const char* GetProfileDirectory(void); + + // List of extensions from addon.xml + const char** GetExtensions(void); + + // Number of extensions + unsigned int GetExtensionCount(void) const { return m_extensions.size(); } + + // Helper functions + void AddProxyDll(const GameClientPtr& gameClient); + bool HasProxyDll(const std::string& strLibPath) const; + + const CGameClient* const m_parent; + game_client_properties m_properties; + + // Buffers to hold the strings + std::string m_strLibraryPath; + std::vector<char*> m_proxyDllPaths; + std::vector<char*> m_resourceDirectories; + std::string m_strProfileDirectory; + std::vector<char*> m_extensions; +}; + +} // namespace GAME diff --git a/xbmc/games/addons/GameClientTiming.cpp b/xbmc/games/addons/GameClientTiming.cpp new file mode 100644 index 0000000000..ccd1533dc8 --- /dev/null +++ b/xbmc/games/addons/GameClientTiming.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GameClientTiming.h" +#include "GameClientCallbacks.h" +#include "utils/MathUtils.h" + +#include <cmath> + +using namespace GAME; + +void CGameClientTiming::Reset() +{ + m_framerate = 0.0; + m_samplerate = 0.0; + m_audioCorrectionFactor = 1.0; +} + +bool CGameClientTiming::NormalizeAudio(IGameAudioCallback* audio) +{ + m_audioCorrectionFactor = audio->NormalizeSamplerate(static_cast<unsigned int>(m_samplerate)) / m_samplerate; + + const double correctionPercent = std::abs(m_audioCorrectionFactor - 1.0) * 100; + + return correctionPercent < MAX_CORRECTION_FACTOR_PERCENT; +} + +double CGameClientTiming::GetFrameRate() const +{ + return m_framerate * m_audioCorrectionFactor; +} + +unsigned int CGameClientTiming::GetSampleRate() const +{ + return MathUtils::round_int(m_samplerate * m_audioCorrectionFactor); +} diff --git a/xbmc/games/addons/GameClientTiming.h b/xbmc/games/addons/GameClientTiming.h new file mode 100644 index 0000000000..65d05fd0c9 --- /dev/null +++ b/xbmc/games/addons/GameClientTiming.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +namespace GAME +{ + class IGameAudioCallback; + + /*! + * \ingroup games + * \brief Class to normalize audio and video timing to avoid audio resampling + * + * For example, assume the audio callback supports two sample rates: + * 32,000 Hz and 44,100 Hz. + * + * If the game client reports an audio sample rate of 32040.5, the audio + * callback will normalize this to 32,000 Hz. The correction factor is then + * set to (32000 / 32040.5) = 0.9987. + * + * After normalization, GetSampleRate() will report (32040.5 * 0.9987) = 32000. + * + * If the game client's frame rate is 60.1 fps, after normalization + * GetFrameRate() will report (60.1 * 0.9987) = 60.024 fps. The game + * client's frame rate has been slowed slightly to avoid resampling audio. + * + * To avoid excessive scaling, normalization will fail if the correction + * factor exceeds MAX_CORRECTION_FACTOR_PERCENT. + */ + class CGameClientTiming + { + public: + static const unsigned int MAX_CORRECTION_FACTOR_PERCENT = 7; + + CGameClientTiming() { Reset(); } + + void Reset(); + + /*! + * \brief Calculate normalization factor to avoid audio resampling + * + * \param audio Callback capable of normalizing sample rate to one of the + * discrete values supported by the audio system + * + * \return false If the correction factor exceeds a pre-defined value, true otherwise + */ + bool NormalizeAudio(IGameAudioCallback* audio); + + // Set frame rate and sample rate reported by the game client + void SetFrameRate(double framerate) { m_framerate = framerate; } + void SetSampleRate(double samplerate) { m_samplerate = samplerate; } + + // Get frame rate and sample rate multiplied by the correction factor + double GetFrameRate() const; + unsigned int GetSampleRate() const; + double GetCorrectionFactor() const { return m_audioCorrectionFactor; } + + private: + double m_framerate; // Video frame rate (fps) + double m_samplerate; // Audio sample rate (Hz) + double m_audioCorrectionFactor; // Factor that audio is normalized by to avoid resampling + }; +} diff --git a/xbmc/games/addons/GameClientTranslator.cpp b/xbmc/games/addons/GameClientTranslator.cpp new file mode 100644 index 0000000000..dda424fc03 --- /dev/null +++ b/xbmc/games/addons/GameClientTranslator.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GameClientTranslator.h" + +using namespace GAME; + +const char* CGameClientTranslator::ToString(GAME_ERROR error) +{ + switch (error) + { + case GAME_ERROR_NO_ERROR: return "no error"; + case GAME_ERROR_NOT_IMPLEMENTED: return "not implemented"; + case GAME_ERROR_REJECTED: return "rejected by the client"; + case GAME_ERROR_INVALID_PARAMETERS: return "invalid parameters for this method"; + case GAME_ERROR_FAILED: return "the command failed"; + case GAME_ERROR_NOT_LOADED: return "no game is loaded"; + case GAME_ERROR_RESTRICTED: return "the required resources are restricted"; + default: + break; + } + return "unknown error"; +} + +AVPixelFormat CGameClientTranslator::TranslatePixelFormat(GAME_PIXEL_FORMAT format) +{ + switch (format) + { + case GAME_PIXEL_FORMAT_YUV420P: return AV_PIX_FMT_YUV420P; + case GAME_PIXEL_FORMAT_0RGB8888: return AV_PIX_FMT_0RGB32; + case GAME_PIXEL_FORMAT_RGB565: return AV_PIX_FMT_RGB565; + case GAME_PIXEL_FORMAT_0RGB1555: return AV_PIX_FMT_RGB555; + default: + break; + } + return AV_PIX_FMT_NONE; +} + +AVCodecID CGameClientTranslator::TranslateVideoCodec(GAME_VIDEO_CODEC codec) +{ + switch (codec) + { + case GAME_VIDEO_CODEC_H264: return AV_CODEC_ID_H264; + default: + break; + } + return AV_CODEC_ID_NONE; +} + +AEDataFormat CGameClientTranslator::TranslatePCMFormat(GAME_PCM_FORMAT format) +{ + switch (format) + { + case GAME_PCM_FORMAT_S16NE: return AE_FMT_S16NE; + default: + break; + } + return AE_FMT_INVALID; +} + +AEChannel CGameClientTranslator::TranslateAudioChannel(GAME_AUDIO_CHANNEL channel) +{ + switch (channel) + { + case GAME_CH_FL: return AE_CH_FL; + case GAME_CH_FR: return AE_CH_FR; + case GAME_CH_FC: return AE_CH_FC; + case GAME_CH_LFE: return AE_CH_LFE; + case GAME_CH_BL: return AE_CH_BL; + case GAME_CH_BR: return AE_CH_BR; + case GAME_CH_FLOC: return AE_CH_FLOC; + case GAME_CH_FROC: return AE_CH_FROC; + case GAME_CH_BC: return AE_CH_BC; + case GAME_CH_SL: return AE_CH_SL; + case GAME_CH_SR: return AE_CH_SR; + case GAME_CH_TFL: return AE_CH_TFL; + case GAME_CH_TFR: return AE_CH_TFR; + case GAME_CH_TFC: return AE_CH_TFC; + case GAME_CH_TC: return AE_CH_TC; + case GAME_CH_TBL: return AE_CH_TBL; + case GAME_CH_TBR: return AE_CH_TBR; + case GAME_CH_TBC: return AE_CH_TBC; + case GAME_CH_BLOC: return AE_CH_BLOC; + case GAME_CH_BROC: return AE_CH_BROC; + default: + break; + } + return AE_CH_NULL; +} + +AVCodecID CGameClientTranslator::TranslateAudioCodec(GAME_AUDIO_CODEC codec) +{ + switch (codec) + { + case GAME_AUDIO_CODEC_OPUS: return AV_CODEC_ID_OPUS; + default: + break; + } + return AV_CODEC_ID_NONE; +} + +GAME_KEY_MOD CGameClientTranslator::GetModifiers(CKey::Modifier modifier) +{ + unsigned int mods = GAME_KEY_MOD_NONE; + + if (modifier & CKey::MODIFIER_CTRL) mods |= GAME_KEY_MOD_CTRL; + if (modifier & CKey::MODIFIER_SHIFT) mods |= GAME_KEY_MOD_SHIFT; + if (modifier & CKey::MODIFIER_ALT) mods |= GAME_KEY_MOD_ALT; + if (modifier & CKey::MODIFIER_RALT) mods |= GAME_KEY_MOD_RALT; + if (modifier & CKey::MODIFIER_META) mods |= GAME_KEY_MOD_META; + + return static_cast<GAME_KEY_MOD>(mods); +} + +const char* CGameClientTranslator::TranslateRegion(GAME_REGION region) +{ + switch (region) + { + case GAME_REGION_NTSC: return "NTSC"; + case GAME_REGION_PAL: return "PAL"; + default: + break; + } + return "Unknown"; +} diff --git a/xbmc/games/addons/GameClientTranslator.h b/xbmc/games/addons/GameClientTranslator.h new file mode 100644 index 0000000000..55a4c94893 --- /dev/null +++ b/xbmc/games/addons/GameClientTranslator.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h" +#include "cores/AudioEngine/Utils/AEChannelData.h" +#include "input/Key.h" + +#include "libavcodec/avcodec.h" +#include "libavutil/pixfmt.h" + +namespace GAME +{ + /*! + * \ingroup games + * \brief Translates data types from Game API to the corresponding format in Kodi. + * + * This class is stateless. + */ + class CGameClientTranslator + { + CGameClientTranslator() = delete; + + public: + /*! + * \brief Translates game errors to string representation (e.g. for logging). + * \param error The error to translate. + * \return Translated error. + */ + static const char* ToString(GAME_ERROR error); + + /*! + * \brief Translate pixel format (Game API to FFMPEG). + * \param format The pixel format to translate. + * \return Translated pixel format. + */ + static AVPixelFormat TranslatePixelFormat(GAME_PIXEL_FORMAT format); + + /*! + * \brief Translate video codec (Game API to FFMPEG). + * \param format The video codec to translate. + * \return Translated video codec format. + */ + static AVCodecID TranslateVideoCodec(GAME_VIDEO_CODEC codec); + + /*! + * \brief Translate audio PCM format (Game API to AudioEngine). + * \param format The audio PCM format to translate. + * \return Translated audio PCM format. + */ + static AEDataFormat TranslatePCMFormat(GAME_PCM_FORMAT format); + + /*! + * \brief Translate audio channels (Game API to AudioEngine). + * \param format The audio channels to translate. + * \return Translated audio channels. + */ + static AEChannel TranslateAudioChannel(GAME_AUDIO_CHANNEL channel); + + /*! + * \brief Translate audio codec (Game API to FFMPEG). + * \param format The audio codec to translate. + * \return Translated audio codec format. + */ + static AVCodecID TranslateAudioCodec(GAME_AUDIO_CODEC codec); + + /*! + * \brief Translate key modifiers (Kodi to Game API). + * \param modifiers The key modifiers to translate (e.g. Shift, Ctrl). + * \return Translated key modifiers. + */ + static GAME_KEY_MOD GetModifiers(CKey::Modifier modifier); + + /*! + * \brief Translate region to string representation (e.g. for logging). + * \param error The region to translate (e.g. PAL, NTSC). + * \return Translated region. + */ + static const char* TranslateRegion(GAME_REGION region); + }; +} diff --git a/xbmc/games/addons/Makefile b/xbmc/games/addons/Makefile new file mode 100644 index 0000000000..6dd4b0333e --- /dev/null +++ b/xbmc/games/addons/Makefile @@ -0,0 +1,12 @@ +SRCS=GameClient.cpp \ + GameClientInput.cpp \ + GameClientKeyboard.cpp \ + GameClientMouse.cpp \ + GameClientProperties.cpp \ + GameClientTiming.cpp \ + GameClientTranslator.cpp \ + +LIB=gameaddons.a + +include ../../../Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) diff --git a/xbmc/games/addons/playback/CMakeLists.txt b/xbmc/games/addons/playback/CMakeLists.txt new file mode 100644 index 0000000000..c4a7ab4662 --- /dev/null +++ b/xbmc/games/addons/playback/CMakeLists.txt @@ -0,0 +1,9 @@ +set(SOURCES GameClientReversiblePlayback.cpp + GameLoop.cpp) + +set(HEADERS GameClientRealtimePlayback.h + GameClientReversiblePlayback.h + GameLoop.h + IGameClientPlayback.h) + +core_add_library(gameplayback) diff --git a/xbmc/games/addons/playback/GameClientRealtimePlayback.h b/xbmc/games/addons/playback/GameClientRealtimePlayback.h new file mode 100644 index 0000000000..2f7c7b4b34 --- /dev/null +++ b/xbmc/games/addons/playback/GameClientRealtimePlayback.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "IGameClientPlayback.h" + +namespace GAME +{ + class CGameClientRealtimePlayback : public IGameClientPlayback + { + public: + virtual ~CGameClientRealtimePlayback() = default; + + // implementation of IGameClientPlayback + virtual bool CanPause() const override { return false; } + virtual bool CanSeek() const override { return false; } + virtual void PauseUnpause() override { } + virtual unsigned int GetTimeMs() const override { return 0; } + virtual unsigned int GetTotalTimeMs() const override { return 0; } + virtual unsigned int GetCacheTimeMs() const override { return 0; } + virtual void SeekTimeMs(unsigned int timeMs) override { } + virtual double GetSpeed() const override { return 1.0; } + virtual void SetSpeed(double speedFactor) override { } + virtual std::string CreateManualSavestate() override { return ""; } + virtual bool LoadSavestate(const std::string& path) override { return false; } + }; +} diff --git a/xbmc/games/addons/playback/GameClientReversiblePlayback.cpp b/xbmc/games/addons/playback/GameClientReversiblePlayback.cpp new file mode 100644 index 0000000000..a75a9c3124 --- /dev/null +++ b/xbmc/games/addons/playback/GameClientReversiblePlayback.cpp @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GameClientReversiblePlayback.h" +#include "games/addons/GameClient.h" +#include "games/addons/savestates/BasicMemoryStream.h" +#include "games/addons/savestates/DeltaPairMemoryStream.h" +#include "games/addons/savestates/Savestate.h" +#include "games/addons/savestates/SavestateReader.h" +#include "games/addons/savestates/SavestateWriter.h" +#include "games/GameSettings.h" +#include "settings/Settings.h" +#include "threads/SingleLock.h" +#include "utils/MathUtils.h" + +#include <algorithm> + +using namespace GAME; + +#define REWIND_FACTOR 0.25 // Rewind at 25% of gameplay speed + +CGameClientReversiblePlayback::CGameClientReversiblePlayback(CGameClient* gameClient, double fps, size_t serializeSize) : + m_gameClient(gameClient), + m_gameLoop(this, fps), + m_savestateWriter(new CSavestateWriter), + m_savestateReader(new CSavestateReader), + m_totalFrameCount(0), + m_pastFrameCount(0), + m_futureFrameCount(0), + m_playTimeMs(0), + m_totalTimeMs(0), + m_cacheTimeMs(0) +{ + UpdateMemoryStream(); + + CGameSettings::GetInstance().RegisterObserver(this); + + m_gameLoop.Start(); +} + +CGameClientReversiblePlayback::~CGameClientReversiblePlayback() +{ + CGameSettings::GetInstance().UnregisterObserver(this); + + m_gameLoop.Stop(); +} + +void CGameClientReversiblePlayback::PauseUnpause() +{ + if (GetSpeed() == 0.0) + m_gameLoop.SetSpeed(1.0); + else + m_gameLoop.SetSpeed(0.0); +} + +void CGameClientReversiblePlayback::SeekTimeMs(unsigned int timeMs) +{ + const int offsetTimeMs = timeMs - GetTimeMs(); + const int offsetFrames = MathUtils::round_int(offsetTimeMs / 1000.0 * m_gameLoop.FPS()); + + if (offsetFrames > 0) + { + const unsigned int frames = std::min(static_cast<unsigned int>(offsetFrames), m_futureFrameCount); + if (frames > 0) + { + m_gameLoop.SetSpeed(0.0); + AdvanceFrames(frames); + m_gameLoop.SetSpeed(1.0); + } + } + else if (offsetFrames < 0) + { + const unsigned int frames = std::min(static_cast<unsigned int>(-offsetFrames), m_pastFrameCount); + if (frames > 0) + { + m_gameLoop.SetSpeed(0.0); + RewindFrames(frames); + m_gameLoop.SetSpeed(1.0); + } + } +} + +double CGameClientReversiblePlayback::GetSpeed() const +{ + return m_gameLoop.GetSpeed(); +} + +void CGameClientReversiblePlayback::SetSpeed(double speedFactor) +{ + if (speedFactor >= 0.0) + m_gameLoop.SetSpeed(speedFactor); + else + m_gameLoop.SetSpeed(speedFactor * REWIND_FACTOR); +} + +std::string CGameClientReversiblePlayback::CreateManualSavestate() +{ + std::string empty; + + // Game client must support serialization + if (m_gameClient->SerializeSize() == 0) + return empty; + + if (!m_savestateWriter->Initialize(m_gameClient, m_totalFrameCount)) + return empty; + + std::unique_ptr<IMemoryStream> memoryStream; + bool bHasMemoryStream = false; + + { + CSingleLock lock(m_mutex); + if (m_memoryStream) + { + memoryStream = std::move(m_memoryStream); + bHasMemoryStream = true; + } + else + { + lock.Leave(); + memoryStream.reset(new CBasicMemoryStream); + memoryStream->Init(m_gameClient->SerializeSize(), 1); + } + } + + // If memory stream is empty, ask the game client for a frame + if (memoryStream->CurrentFrame() == nullptr) + { + if (m_gameClient->Serialize(memoryStream->BeginFrame(), memoryStream->FrameSize())) + memoryStream->SubmitFrame(); + } + + bool bSuccess = false; + + if (m_savestateWriter->WriteSave(memoryStream.get())) + { + m_savestateWriter->WriteThumb(); + + if (m_savestateWriter->CommitToDatabase()) + bSuccess = true; + else + m_savestateWriter->CleanUpTransaction(); + } + + if (bHasMemoryStream) + { + CSingleLock lock(m_mutex); + m_memoryStream = std::move(memoryStream); + } + + return bSuccess ? m_savestateWriter->GetPath() : ""; +} + +bool CGameClientReversiblePlayback::LoadSavestate(const std::string& path) +{ + // Game client must support serialization + if (m_gameClient->SerializeSize() == 0) + return false; + + if (!m_savestateReader->Initialize(path, m_gameClient)) + return false; + + std::unique_ptr<IMemoryStream> memoryStream; + bool bHasMemoryStream = false; + + { + CSingleLock lock(m_mutex); + if (m_memoryStream) + { + memoryStream = std::move(m_memoryStream); + bHasMemoryStream = true; + } + else + { + lock.Leave(); + memoryStream.reset(new CBasicMemoryStream); + memoryStream->Init(m_gameClient->SerializeSize(), 1); + } + } + + bool bSuccess = false; + + if (m_savestateReader->ReadSave(memoryStream.get())) + { + m_gameClient->Deserialize(memoryStream->CurrentFrame(), memoryStream->FrameSize()); + m_totalFrameCount = m_savestateReader->GetFrameCount(); + bSuccess = true; + } + + if (bHasMemoryStream) + { + CSingleLock lock(m_mutex); + m_memoryStream = std::move(memoryStream); + } + + return bSuccess; +} + +void CGameClientReversiblePlayback::FrameEvent() +{ + m_gameClient->RunFrame(); + + AddFrame(); +} + +void CGameClientReversiblePlayback::RewindEvent() +{ + RewindFrames(1); + + m_gameClient->RunFrame(); +} + +void CGameClientReversiblePlayback::AddFrame() +{ + CSingleLock lock(m_mutex); + + if (m_memoryStream) + { + if (m_gameClient->Serialize(m_memoryStream->BeginFrame(), m_memoryStream->FrameSize())) + { + m_memoryStream->SubmitFrame(); + UpdatePlaybackStats(); + } + } + + m_totalFrameCount++; +} + +void CGameClientReversiblePlayback::RewindFrames(unsigned int frames) +{ + CSingleLock lock(m_mutex); + + if (m_memoryStream) + { + m_memoryStream->RewindFrames(frames); + m_gameClient->Deserialize(m_memoryStream->CurrentFrame(), m_memoryStream->FrameSize()); + UpdatePlaybackStats(); + } + + m_totalFrameCount -= std::min(m_totalFrameCount, static_cast<uint64_t>(frames)); +} + +void CGameClientReversiblePlayback::AdvanceFrames(unsigned int frames) +{ + CSingleLock lock(m_mutex); + + if (m_memoryStream) + { + m_memoryStream->AdvanceFrames(frames); + m_gameClient->Deserialize(m_memoryStream->CurrentFrame(), m_memoryStream->FrameSize()); + UpdatePlaybackStats(); + } + + m_totalFrameCount += frames; +} + +void CGameClientReversiblePlayback::UpdatePlaybackStats() +{ + m_pastFrameCount = m_memoryStream->PastFramesAvailable(); + m_futureFrameCount = m_memoryStream->FutureFramesAvailable(); + + const unsigned int played = m_pastFrameCount + (m_memoryStream->CurrentFrame() ? 1 : 0); + const unsigned int total = m_memoryStream->MaxFrameCount(); + const unsigned int cached = m_futureFrameCount; + + m_playTimeMs = MathUtils::round_int(1000.0 * played / m_gameLoop.FPS()); + m_totalTimeMs = MathUtils::round_int(1000.0 * total / m_gameLoop.FPS()); + m_cacheTimeMs = MathUtils::round_int(1000.0 * cached / m_gameLoop.FPS()); +} + +void CGameClientReversiblePlayback::Notify(const Observable &obs, const ObservableMessage msg) +{ + switch (msg) + { + case ObservableMessageSettingsChanged: + UpdateMemoryStream(); + break; + default: + break; + } +} + +void CGameClientReversiblePlayback::UpdateMemoryStream() +{ + CSingleLock lock(m_mutex); + + bool bRewindEnabled = false; + + if (m_gameClient->SerializeSize() > 0) + bRewindEnabled = CSettings::GetInstance().GetBool(CSettings::SETTING_GAMES_ENABLEREWIND); + + if (bRewindEnabled) + { + unsigned int rewindBufferSec = CSettings::GetInstance().GetInt(CSettings::SETTING_GAMES_REWINDTIME); + if (rewindBufferSec < 10) + rewindBufferSec = 10; // Sanity check + + unsigned int frameCount = MathUtils::round_int(rewindBufferSec * m_gameLoop.FPS()); + + if (!m_memoryStream) + { + m_memoryStream.reset(new CDeltaPairMemoryStream); + m_memoryStream->Init(m_gameClient->SerializeSize(), frameCount); + } + + if (m_memoryStream->MaxFrameCount() != frameCount) + { + m_memoryStream->SetMaxFrameCount(frameCount); + } + } + else + { + m_memoryStream.reset(); + + // Reset playback stats + m_pastFrameCount = 0; + m_futureFrameCount = 0; + m_playTimeMs = 0; + m_totalTimeMs = 0; + m_cacheTimeMs = 0; + } +} diff --git a/xbmc/games/addons/playback/GameClientReversiblePlayback.h b/xbmc/games/addons/playback/GameClientReversiblePlayback.h new file mode 100644 index 0000000000..aea47cbb13 --- /dev/null +++ b/xbmc/games/addons/playback/GameClientReversiblePlayback.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "IGameClientPlayback.h" +#include "GameLoop.h" +#include "threads/CriticalSection.h" +#include "utils/Observer.h" + +#include <memory> +#include <stddef.h> +#include <stdint.h> + +namespace GAME +{ + class CGameClient; + class CSavestateReader; + class CSavestateWriter; + class IMemoryStream; + + class CGameClientReversiblePlayback : public IGameClientPlayback, + public IGameLoopCallback, + public Observer + { + public: + CGameClientReversiblePlayback(CGameClient* gameClient, double fps, size_t serializeSize); + + virtual ~CGameClientReversiblePlayback(); + + // implementation of IGameClientPlayback + virtual bool CanPause() const override { return true; } + virtual bool CanSeek() const override { return true; } + virtual void PauseUnpause() override; + virtual unsigned int GetTimeMs() const override { return m_playTimeMs; } + virtual unsigned int GetTotalTimeMs() const override { return m_totalTimeMs; } + virtual unsigned int GetCacheTimeMs() const override { return m_cacheTimeMs; } + virtual void SeekTimeMs(unsigned int timeMs) override; + virtual double GetSpeed() const override; + virtual void SetSpeed(double speedFactor) override; + virtual std::string CreateManualSavestate() override; + virtual bool LoadSavestate(const std::string& path) override; + + // implementation of IGameLoopCallback + virtual void FrameEvent() override; + virtual void RewindEvent() override; + + // implementation of Observer + virtual void Notify(const Observable &obs, const ObservableMessage msg) override; + + private: + void AddFrame(); + void RewindFrames(unsigned int frames); + void AdvanceFrames(unsigned int frames); + void UpdatePlaybackStats(); + void UpdateMemoryStream(); + + // Construction parameter + CGameClient* const m_gameClient; + + // Gameplay functionality + CGameLoop m_gameLoop; + std::unique_ptr<IMemoryStream> m_memoryStream; + CCriticalSection m_mutex; + + // Savestate functionality + std::unique_ptr<CSavestateWriter> m_savestateWriter; + std::unique_ptr<CSavestateReader> m_savestateReader; + + // Playback stats + uint64_t m_totalFrameCount; + unsigned int m_pastFrameCount; + unsigned int m_futureFrameCount; + unsigned int m_playTimeMs; + unsigned int m_totalTimeMs; + unsigned int m_cacheTimeMs; + }; +} diff --git a/xbmc/games/addons/playback/GameLoop.cpp b/xbmc/games/addons/playback/GameLoop.cpp new file mode 100644 index 0000000000..f091b32da1 --- /dev/null +++ b/xbmc/games/addons/playback/GameLoop.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GameLoop.h" +#include "games/addons/GameClient.h" +#include "threads/SingleLock.h" +#include "threads/SystemClock.h" + +#include <cmath> + +using namespace GAME; + +#define DEFAULT_FPS 60 // In case fps is 0 (shouldn't happen) +#define FOREVER_MS (7 * 24 * 60 * 60 * 1000) // 1 week is large enough + +CGameLoop::CGameLoop(IGameLoopCallback* callback, double fps) : + CThread("GameLoop"), + m_callback(callback), + m_fps(fps ? fps : DEFAULT_FPS), + m_speedFactor(0.0), + m_lastFrameMs(0.0) +{ +} + +void CGameLoop::Start() +{ + Create(); +} + +void CGameLoop::Stop() +{ + StopThread(false); + m_sleepEvent.Set(); + StopThread(true); +} + +void CGameLoop::SetSpeed(double speedFactor) +{ + { + CSingleLock lock(m_mutex); + m_speedFactor = speedFactor; + } + m_sleepEvent.Set(); +} + +void CGameLoop::Process(void) +{ + double nextFrameMs = NowMs(); + + CSingleLock lock(m_mutex); + + while (!m_bStop) + { + double speedFactor = m_speedFactor; + + { + CSingleExit exit(m_mutex); + if (speedFactor > 0.0) + m_callback->FrameEvent(); + else if (speedFactor < 0.0) + m_callback->RewindEvent(); + } + + // Record frame time + m_lastFrameMs = nextFrameMs; + + // Calculate sleep time + double nowMs = NowMs(); + double sleepTimeMs = SleepTimeMs(nowMs); + + // Sleep at least 1 ms to avoid sleeping forever + while (sleepTimeMs > 1.0) + { + { + CSingleExit exit(m_mutex); + m_sleepEvent.WaitMSec(static_cast<unsigned int>(sleepTimeMs)); + } + + if (m_bStop) + break; + + // Speed may have changed, update sleep time + nowMs = NowMs(); + sleepTimeMs = SleepTimeMs(nowMs); + } + + // Calculate next frame time + nextFrameMs += FrameTimeMs(); + + // If sleep time goes negative, we fell behind, so fast-forward to now + if (sleepTimeMs < 0.0) + nextFrameMs = nowMs; + } +} + +double CGameLoop::FrameTimeMs() const +{ + if (m_speedFactor != 0.0) + return 1000.0 / m_fps / std::abs(m_speedFactor); + else + return FOREVER_MS; +} + +double CGameLoop::SleepTimeMs(double nowMs) const +{ + // Calculate next frame time + const double nextFrameMs = m_lastFrameMs + FrameTimeMs(); + + // Calculate sleep time + const double sleepTimeMs = nextFrameMs - nowMs; + + return sleepTimeMs; +} + +double CGameLoop::NowMs() const +{ + return static_cast<double>(XbmcThreads::SystemClockMillis()); +} diff --git a/xbmc/games/addons/playback/GameLoop.h b/xbmc/games/addons/playback/GameLoop.h new file mode 100644 index 0000000000..70ca8b6419 --- /dev/null +++ b/xbmc/games/addons/playback/GameLoop.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "threads/CriticalSection.h" +#include "threads/Event.h" +#include "threads/Thread.h" + +namespace GAME +{ + class IGameLoopCallback + { + public: + virtual ~IGameLoopCallback() = default; + + /*! + * \brief The next frame is being shown + */ + virtual void FrameEvent() = 0; + + /*! + * \brief The prior frame is being shown + */ + virtual void RewindEvent() = 0; + }; + + class CGameLoop : protected CThread + { + public: + CGameLoop(IGameLoopCallback* callback, double fps); + + void Start(); + void Stop(); + + double FPS() const { return m_fps; } + + double GetSpeed() const { return m_speedFactor; } + void SetSpeed(double speedFactor); + + protected: + // implementation of CThread + virtual void Process() override; + + private: + double FrameTimeMs() const; + double SleepTimeMs(double nowMs) const; + double NowMs() const; + + IGameLoopCallback* const m_callback; + const double m_fps; + double m_speedFactor; + double m_lastFrameMs; + CEvent m_sleepEvent; + CCriticalSection m_mutex; + }; +} diff --git a/xbmc/games/addons/playback/IGameClientPlayback.h b/xbmc/games/addons/playback/IGameClientPlayback.h new file mode 100644 index 0000000000..4e9b09e6bc --- /dev/null +++ b/xbmc/games/addons/playback/IGameClientPlayback.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include <stdint.h> +#include <string> + +namespace GAME +{ + class IGameClientPlayback + { + public: + virtual ~IGameClientPlayback() = default; + + // Playback capabilities + virtual bool CanPause() const = 0; + virtual bool CanSeek() const = 0; + + // Control playback + virtual void PauseUnpause() = 0; + virtual unsigned int GetTimeMs() const = 0; + virtual unsigned int GetTotalTimeMs() const = 0; + virtual unsigned int GetCacheTimeMs() const = 0; + virtual void SeekTimeMs(unsigned int timeMs) = 0; + virtual double GetSpeed() const = 0; + virtual void SetSpeed(double speedFactor) = 0; + + // Savestates + virtual std::string CreateManualSavestate() = 0; // Returns the path of savestate on success + virtual bool LoadSavestate(const std::string& path) = 0; + }; +} diff --git a/xbmc/games/addons/playback/Makefile b/xbmc/games/addons/playback/Makefile new file mode 100644 index 0000000000..3a92133b31 --- /dev/null +++ b/xbmc/games/addons/playback/Makefile @@ -0,0 +1,7 @@ +SRCS=GameClientReversiblePlayback.cpp \ + GameLoop.cpp \ + +LIB=gameaddonplayback.a + +include ../../../../Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) diff --git a/xbmc/games/addons/savestates/BasicMemoryStream.cpp b/xbmc/games/addons/savestates/BasicMemoryStream.cpp new file mode 100644 index 0000000000..057050dad6 --- /dev/null +++ b/xbmc/games/addons/savestates/BasicMemoryStream.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "BasicMemoryStream.h" + +using namespace GAME; + +CBasicMemoryStream::CBasicMemoryStream() +{ + Reset(); +} + +void CBasicMemoryStream::Init(size_t frameSize, size_t maxFrameCount) +{ + Reset(); + + m_frameSize = frameSize; +} + +void CBasicMemoryStream::Reset() +{ + m_frameSize = 0; + m_frameBuffer.reset(); + m_bHasFrame = false; +} + +uint8_t* CBasicMemoryStream::BeginFrame() +{ + if (m_frameSize == 0) + return nullptr; + + if (!m_frameBuffer) + m_frameBuffer.reset(new uint8_t[m_frameSize]); + + m_bHasFrame = false; + + return m_frameBuffer.get(); +} + +void CBasicMemoryStream::SubmitFrame() +{ + if (m_frameBuffer) + m_bHasFrame = true; +} + +const uint8_t* CBasicMemoryStream::CurrentFrame() const +{ + return m_bHasFrame ? m_frameBuffer.get() : nullptr; +} diff --git a/xbmc/games/addons/savestates/BasicMemoryStream.h b/xbmc/games/addons/savestates/BasicMemoryStream.h new file mode 100644 index 0000000000..2191bb791b --- /dev/null +++ b/xbmc/games/addons/savestates/BasicMemoryStream.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "IMemoryStream.h" + +#include <memory> + +namespace GAME +{ + class CBasicMemoryStream : public IMemoryStream + { + public: + CBasicMemoryStream(); + + virtual ~CBasicMemoryStream() = default; + + // implementation of IMemoryStream + virtual void Init(size_t frameSize, size_t maxFrameCount) override; + virtual void Reset() override; + virtual size_t FrameSize() const override { return m_frameSize; } + virtual unsigned int MaxFrameCount() const override { return 1; } + virtual void SetMaxFrameCount(size_t maxFrameCount) override { } + virtual uint8_t* BeginFrame() override; + virtual void SubmitFrame() override; + virtual const uint8_t* CurrentFrame() const override; + virtual unsigned int FutureFramesAvailable() const override { return 0; } + virtual unsigned int AdvanceFrames(unsigned int frameCount) override { return 0; } + virtual unsigned int PastFramesAvailable() const override { return 0; } + virtual unsigned int RewindFrames(unsigned int frameCount) override { return 0; } + virtual uint64_t GetFrameCounter() const override { return 0; } + virtual void SetFrameCounter(uint64_t frameCount) override { }; + + private: + size_t m_frameSize; + std::unique_ptr<uint8_t[]> m_frameBuffer; + bool m_bHasFrame; + }; +} diff --git a/xbmc/games/addons/savestates/CMakeLists.txt b/xbmc/games/addons/savestates/CMakeLists.txt new file mode 100644 index 0000000000..984bf423ac --- /dev/null +++ b/xbmc/games/addons/savestates/CMakeLists.txt @@ -0,0 +1,23 @@ +set(SOURCES BasicMemoryStream.cpp + DeltaPairMemoryStream.cpp + LinearMemoryStream.cpp + Savestate.cpp + SavestateDatabase.cpp + SavestateReader.cpp + SavestateTranslator.cpp + SavestateUtils.cpp + SavestateWriter.cpp) + +set(HEADERS BasicMemoryStream.h + DeltaPairMemoryStream.h + IMemoryStream.h + LinearMemoryStream.h + Savestate.h + SavestateDatabase.h + SavestateDefines.h + SavestateReader.h + SavestateTranslator.h + SavestateUtils.h + SavestateWriter.h) + +core_add_library(gamesavestates) diff --git a/xbmc/games/addons/savestates/DeltaPairMemoryStream.cpp b/xbmc/games/addons/savestates/DeltaPairMemoryStream.cpp new file mode 100644 index 0000000000..4cc303f3e2 --- /dev/null +++ b/xbmc/games/addons/savestates/DeltaPairMemoryStream.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "DeltaPairMemoryStream.h" +#include "utils/log.h" + +using namespace GAME; + +void CDeltaPairMemoryStream::Reset() +{ + CLinearMemoryStream::Reset(); + + m_rewindBuffer.clear(); +} + +void CDeltaPairMemoryStream::SubmitFrameInternal() +{ + m_rewindBuffer.push_back(MemoryFrame()); + MemoryFrame& frame = m_rewindBuffer.back(); + + // Record frame history + frame.frameHistoryCount = m_currentFrameHistory++; + + uint32_t* currentFrame = m_currentFrame.get(); + uint32_t* nextFrame = m_nextFrame.get(); + + for (size_t i = 0; i < m_paddedFrameSize; i++) + { + uint32_t xor_val = currentFrame[i] ^ nextFrame[i]; + if (xor_val) + { + DeltaPair pair = { i, xor_val }; + frame.buffer.push_back(pair); + } + } + + // Delta is generated, bring the new frame forward (m_nextFrame is now disposable) + std::swap(m_currentFrame, m_nextFrame); + + m_bHasNextFrame = false; + + if (PastFramesAvailable() + 1 > MaxFrameCount()) + CullPastFrames(1); +} + +unsigned int CDeltaPairMemoryStream::PastFramesAvailable() const +{ + return static_cast<unsigned int>(m_rewindBuffer.size()); +} + +unsigned int CDeltaPairMemoryStream::RewindFrames(unsigned int frameCount) +{ + unsigned int rewound; + + for (rewound = 0; rewound < frameCount; rewound++) + { + if (m_rewindBuffer.empty()) + break; + + const MemoryFrame& frame = m_rewindBuffer.back(); + const DeltaPair* buffer = frame.buffer.data(); + + size_t bufferSize = frame.buffer.size(); + + // buffer pointer redirection violates data-dependency requirements... + // no vectorization for us :( + for (size_t i = 0; i < bufferSize; i++) + m_currentFrame[buffer[i].pos] ^= buffer[i].delta; + + // Restore frame history + m_currentFrameHistory = frame.frameHistoryCount; + + m_rewindBuffer.pop_back(); + } + + return rewound; +} + +void CDeltaPairMemoryStream::CullPastFrames(unsigned int frameCount) +{ + for (unsigned int removedCount = 0; removedCount < frameCount; removedCount++) + { + if (m_rewindBuffer.empty()) + { + CLog::Log(LOGDEBUG, "CDeltaPairMemoryStream: Tried to cull %d frames too many. Check your math!", frameCount - removedCount); + break; + } + m_rewindBuffer.pop_front(); + } +} diff --git a/xbmc/games/addons/savestates/DeltaPairMemoryStream.h b/xbmc/games/addons/savestates/DeltaPairMemoryStream.h new file mode 100644 index 0000000000..b4ce01426c --- /dev/null +++ b/xbmc/games/addons/savestates/DeltaPairMemoryStream.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "LinearMemoryStream.h" + +#include <deque> +#include <vector> + +namespace GAME +{ + /*! + * \brief Implementation of a linear memory stream using XOR deltas + */ + class CDeltaPairMemoryStream : public CLinearMemoryStream + { + public: + CDeltaPairMemoryStream() = default; + + virtual ~CDeltaPairMemoryStream() = default; + + // implementation of IMemoryStream via CLinearMemoryStream + virtual void Reset() override; + virtual unsigned int PastFramesAvailable() const override; + virtual unsigned int RewindFrames(unsigned int frameCount) override; + + protected: + // implementation of CLinearMemoryStream + virtual void SubmitFrameInternal() override; + virtual void CullPastFrames(unsigned int frameCount) override; + + /*! + * Rewinding is implemented by applying XOR deltas on the specific parts of + * the save state buffer which have changed. In practice, this is very fast + * and simple (linear scan) and allows deltas to be compressed down to 1-3% + * of original save state size depending on the system. The algorithm runs + * on 32 bits at a time for speed. + * + * Use std::deque here to achieve amortized O(1) on pop/push to front and + * back. + */ + struct DeltaPair + { + size_t pos; + uint32_t delta; + }; + + typedef std::vector<DeltaPair> DeltaPairVector; + + struct MemoryFrame + { + DeltaPairVector buffer; + uint64_t frameHistoryCount; + }; + + std::deque<MemoryFrame> m_rewindBuffer; + }; +} diff --git a/xbmc/games/addons/savestates/IMemoryStream.h b/xbmc/games/addons/savestates/IMemoryStream.h new file mode 100644 index 0000000000..b5523ea93c --- /dev/null +++ b/xbmc/games/addons/savestates/IMemoryStream.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include <stddef.h> +#include <stdint.h> + +namespace GAME +{ + /*! + * \brief Stream of serialized states from game clients + * + * A memory stream is composed of "frames" of memory representing serialized + * states of the game client. For each video frame run by the game loop, the + * game client's state is serialized into a buffer provided by this interface. + * + * Implementation of three types of memory streams are provided: + * + * - Basic memory stream: has only a current frame, and supports neither + * rewind nor forward seeking. + * + * \sa CBasicMemoryStream + * + * - Linear memory stream: can grow in one direction. It is possible to + * rewind, but not fast-forward. + * + * \sa CLinearMemoryStream + * + * - Nonlinear memory stream: can have frames both ahead of and behind + * the current frame. If a stream is rewound, it is possible to + * recover these frames by seeking forward again. + * + * \sa CNonlinearMemoryStream (TODO) + */ + class IMemoryStream + { + public: + virtual ~IMemoryStream() = default; + + /*! + * \brief Initialize memory stream + * + * \param frameSize The size of the serialized memory state + * \param maxFrameCount The maximum number of frames this steam can hold + */ + virtual void Init(size_t frameSize, size_t maxFrameCount) = 0; + + /*! + * \brief Free any resources used by this stream + */ + virtual void Reset() = 0; + + /*! + * \brief Return the frame size passed to Init() + */ + virtual size_t FrameSize() const = 0; + + /*! + * \brief Return the current max frame count + */ + virtual unsigned int MaxFrameCount() const = 0; + + /*! + * \brief Update the max frame count + * + * Old frames may be deleted if the max frame count is reduced. + */ + virtual void SetMaxFrameCount(size_t maxFrameCount) = 0; + + /*! + * \ brief Get a pointer to which FrameSize() bytes can be written + * + * The buffer exposed by this function is passed to the game client, which + * fills it with a serialization of its current state. + */ + virtual uint8_t* BeginFrame() = 0; + + /*! + * \brief Indicate that a frame of size FrameSize() has been written to the + * location returned from BeginFrame() + */ + virtual void SubmitFrame() = 0; + + /*! + * \brief Get a pointer to the current frame + * + * This function must have no side effects. The pointer is valid until the + * stream is modified. + * + * \return A buffer of size FrameSize(), or nullptr if the stream is empty + */ + virtual const uint8_t* CurrentFrame() const = 0; + + /*! + * \brief Return the number of frames ahead of the current frame + * + * If the stream supports forward seeking, frames that are passed over + * during a "rewind" operation can be recovered again. + */ + virtual unsigned int FutureFramesAvailable() const = 0; + + /*! + * \brief Seek ahead the specified number of frames + * + * \return The number of frames advanced + */ + virtual unsigned int AdvanceFrames(unsigned int frameCount) = 0; + + /*! + * \brief Return the number of frames behind the current frame + */ + virtual unsigned int PastFramesAvailable() const = 0; + + /*! + * \brief Seek backwards the specified number of frames + * + * \return The number of frames rewound + */ + virtual unsigned int RewindFrames(unsigned int frameCount) = 0; + + /*! + * \brief Get the total number of frames played until the current frame + * + * \return The history of the current frame, or 0 for unknown + */ + virtual uint64_t GetFrameCounter() const = 0; + + /*! + * \brief Set the total number of frames played until the current frame + * + * \param frameCount The history of the current frame + */ + virtual void SetFrameCounter(uint64_t frameCount) = 0; + }; +} diff --git a/xbmc/games/addons/savestates/LinearMemoryStream.cpp b/xbmc/games/addons/savestates/LinearMemoryStream.cpp new file mode 100644 index 0000000000..2e54859112 --- /dev/null +++ b/xbmc/games/addons/savestates/LinearMemoryStream.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "LinearMemoryStream.h" + +using namespace GAME; + +// Pad forward to nearest boundary of bytes +#define PAD_TO_CEIL(x, bytes) (((x) + (bytes) - 1) / (bytes)) + +CLinearMemoryStream::CLinearMemoryStream() +{ + Reset(); +} + +void CLinearMemoryStream::Init(size_t frameSize, size_t maxFrameCount) +{ + Reset(); + + m_frameSize = frameSize; + m_paddedFrameSize = PAD_TO_CEIL(m_frameSize, sizeof(uint32_t)); + m_maxFrames = maxFrameCount; +} + +void CLinearMemoryStream::Reset() +{ + m_frameSize = 0; + m_paddedFrameSize = 0; + m_maxFrames = 0; + m_currentFrame.reset(); + m_nextFrame.reset(); + m_bHasCurrentFrame = false; + m_bHasNextFrame = false; + m_currentFrameHistory = 0; +} + +void CLinearMemoryStream::SetMaxFrameCount(size_t maxFrameCount) +{ + if (maxFrameCount == 0) + { + Reset(); + } + else + { + const unsigned int frameCount = BufferSize(); + if (maxFrameCount < frameCount) + CullPastFrames(frameCount - maxFrameCount); + } + + m_maxFrames = maxFrameCount; +} + +uint8_t* CLinearMemoryStream::BeginFrame() +{ + if (m_paddedFrameSize == 0) + return nullptr; + + if (!m_bHasCurrentFrame) + { + if (!m_currentFrame) + m_currentFrame.reset(new uint32_t[m_paddedFrameSize]); + return reinterpret_cast<uint8_t*>(m_currentFrame.get()); + } + + if (!m_nextFrame) + m_nextFrame.reset(new uint32_t[m_paddedFrameSize]); + return reinterpret_cast<uint8_t*>(m_nextFrame.get()); +} + +const uint8_t* CLinearMemoryStream::CurrentFrame() const +{ + if (m_bHasCurrentFrame) + return reinterpret_cast<const uint8_t*>(m_currentFrame.get()); + + return nullptr; +} + +void CLinearMemoryStream::SubmitFrame() +{ + if (!m_bHasCurrentFrame) + { + m_bHasCurrentFrame = true; + } + else if (!m_bHasNextFrame) + { + m_bHasNextFrame = true; + } + + if (m_bHasNextFrame) + { + SubmitFrameInternal(); + } +} + +unsigned int CLinearMemoryStream::BufferSize() const +{ + return PastFramesAvailable() + (m_bHasCurrentFrame ? 1 : 0); +} diff --git a/xbmc/games/addons/savestates/LinearMemoryStream.h b/xbmc/games/addons/savestates/LinearMemoryStream.h new file mode 100644 index 0000000000..f5c5578667 --- /dev/null +++ b/xbmc/games/addons/savestates/LinearMemoryStream.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "IMemoryStream.h" + +#include <memory> + +namespace GAME +{ + class CLinearMemoryStream : public IMemoryStream + { + public: + CLinearMemoryStream(); + + virtual ~CLinearMemoryStream() = default; + + // partial implementation of IMemoryStream + virtual void Init(size_t frameSize, size_t maxFrameCount) override; + virtual void Reset() override; + virtual size_t FrameSize() const override { return m_frameSize; } + virtual unsigned int MaxFrameCount() const override { return m_maxFrames; } + virtual void SetMaxFrameCount(size_t maxFrameCount) override; + virtual uint8_t* BeginFrame() override; + virtual void SubmitFrame() override; + virtual const uint8_t* CurrentFrame() const override; + virtual unsigned int FutureFramesAvailable() const override { return 0; } + virtual unsigned int AdvanceFrames(unsigned int frameCount) override { return 0; } + virtual unsigned int PastFramesAvailable() const override = 0; + virtual unsigned int RewindFrames(unsigned int frameCount) override = 0; + virtual uint64_t GetFrameCounter() const override { return m_currentFrameHistory; } + virtual void SetFrameCounter(uint64_t frameCount) override { m_currentFrameHistory = frameCount; } + + protected: + virtual void SubmitFrameInternal() = 0; + virtual void CullPastFrames(unsigned int frameCount) = 0; + + // Helper function + unsigned int BufferSize() const; + + size_t m_paddedFrameSize; + unsigned int m_maxFrames; + + /** + * Simple double-buffering. After XORing the two states, the next becomes + * the current, and the current becomes a buffer for the next call to + * CGameClient::Serialize(). + */ + std::unique_ptr<uint32_t[]> m_currentFrame; + std::unique_ptr<uint32_t[]> m_nextFrame; + bool m_bHasCurrentFrame; + bool m_bHasNextFrame; + + uint64_t m_currentFrameHistory; + + private: + size_t m_frameSize; + }; +} diff --git a/xbmc/games/addons/savestates/Makefile b/xbmc/games/addons/savestates/Makefile new file mode 100644 index 0000000000..178e6db8a4 --- /dev/null +++ b/xbmc/games/addons/savestates/Makefile @@ -0,0 +1,14 @@ +SRCS=BasicMemoryStream.cpp \ + DeltaPairMemoryStream.cpp \ + LinearMemoryStream.cpp \ + Savestate.cpp \ + SavestateDatabase.cpp \ + SavestateReader.cpp \ + SavestateTranslator.cpp \ + SavestateUtils.cpp \ + SavestateWriter.cpp \ + +LIB=gamesavestates.a + +include ../../../../Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) diff --git a/xbmc/games/addons/savestates/Savestate.cpp b/xbmc/games/addons/savestates/Savestate.cpp new file mode 100644 index 0000000000..763382ea71 --- /dev/null +++ b/xbmc/games/addons/savestates/Savestate.cpp @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Savestate.h" +#include "SavestateDefines.h" +#include "SavestateTranslator.h" +#include "utils/log.h" +#include "utils/Variant.h" +#include "utils/XMLUtils.h" + +#include <tinyxml.h> + +using namespace GAME; + +void CSavestate::Reset() +{ + m_path.clear(); + m_type = SAVETYPE::UNKNOWN; + m_slot = -1; + m_label.clear(); + m_size = 0; + m_gameClient.clear(); + m_databaseId = -1; + m_gamePath.clear(); + m_gameCRC.clear(); + m_playtimeFrames = 0; + m_playtimeWallClock = 0.0; + m_timestamp.Reset(); + m_thumbnail.clear(); +} + +void CSavestate::Serialize(CVariant& value) const +{ + value[SAVESTATE_FIELD_PATH] = m_path; + value[SAVESTATE_FIELD_TYPE] = static_cast<unsigned int>(m_type); + value[SAVESTATE_FIELD_SLOT] = m_slot; + value[SAVESTATE_FIELD_LABEL] = m_label; + value[SAVESTATE_FIELD_SIZE] = static_cast<unsigned int>(m_size); + value[SAVESTATE_FIELD_GAMECLIENT] = m_gameClient; + value[SAVESTATE_FIELD_DB_ID] = m_databaseId; + value[SAVESTATE_FIELD_GAME_PATH] = m_gamePath; + value[SAVESTATE_FIELD_GAME_CRC] = m_gameCRC; + value[SAVESTATE_FIELD_FRAMES] = m_playtimeFrames; + value[SAVESTATE_FIELD_WALLCLOCK] = m_playtimeWallClock; + value[SAVESTATE_FIELD_TIMESTAMP] = m_timestamp.GetAsDBDateTime(); + value[SAVESTATE_FIELD_THUMBNAIL] = m_thumbnail; +} + +void CSavestate::Deserialize(const CVariant& value) +{ + m_path = value[SAVESTATE_FIELD_PATH].asString(); + m_type = static_cast<SAVETYPE>(value[SAVESTATE_FIELD_TYPE].asInteger()); + m_slot = static_cast<int>(value[SAVESTATE_FIELD_SLOT].asInteger()); + m_label = value[SAVESTATE_FIELD_LABEL].asString(); + m_size = static_cast<size_t>(value[SAVESTATE_FIELD_SIZE].asUnsignedInteger()); + m_gameClient = value[SAVESTATE_FIELD_GAMECLIENT].asString(); + m_databaseId = static_cast<int>(value[SAVESTATE_FIELD_DB_ID].asInteger()); + m_gamePath = value[SAVESTATE_FIELD_GAME_PATH].asString(); + m_gameCRC = value[SAVESTATE_FIELD_GAME_CRC].asString(); + m_playtimeFrames = value[SAVESTATE_FIELD_FRAMES].asUnsignedInteger(); + m_playtimeWallClock = value[SAVESTATE_FIELD_WALLCLOCK].asDouble(); + m_timestamp.SetFromDBDateTime(value[SAVESTATE_FIELD_TIMESTAMP].asString()); + m_thumbnail = value[SAVESTATE_FIELD_THUMBNAIL].asString(); +} + +bool CSavestate::Serialize(const std::string& path) const +{ + if (m_type == SAVETYPE::UNKNOWN) + { + CLog::Log(LOGERROR, "Failed to serialize savestate (unknown type)"); + return false; + } + + TiXmlDocument xmlFile; + + TiXmlDeclaration* decl = new TiXmlDeclaration("1.0", "", ""); + xmlFile.LinkEndChild(decl); + + TiXmlElement rootElement(SAVESTATE_XML_ROOT); + TiXmlNode* root = xmlFile.InsertEndChild(rootElement); + if (root == NULL) + return false; + + TiXmlElement* pElement = root->ToElement(); + if (!pElement) + return false; + + XMLUtils::SetString(pElement, SAVESTATE_FIELD_PATH, m_path); + XMLUtils::SetString(pElement, SAVESTATE_FIELD_TYPE, CSavestateTranslator::TranslateType(m_type)); + if (m_type == SAVETYPE::SLOT) + XMLUtils::SetInt(pElement, SAVESTATE_FIELD_SLOT, m_slot); + XMLUtils::SetString(pElement, SAVESTATE_FIELD_LABEL, m_label); + XMLUtils::SetLong(pElement, SAVESTATE_FIELD_SIZE, m_size); + XMLUtils::SetString(pElement, SAVESTATE_FIELD_GAMECLIENT, m_gameClient); + XMLUtils::SetString(pElement, SAVESTATE_FIELD_GAME_PATH, m_gamePath); + XMLUtils::SetString(pElement, SAVESTATE_FIELD_GAME_CRC, m_gameCRC); + XMLUtils::SetLong(pElement, SAVESTATE_FIELD_FRAMES, static_cast<long>(m_playtimeFrames)); + XMLUtils::SetFloat(pElement, SAVESTATE_FIELD_WALLCLOCK, static_cast<float>(m_playtimeWallClock)); + XMLUtils::SetString(pElement, SAVESTATE_FIELD_TIMESTAMP, m_timestamp.GetAsDBDateTime()); + XMLUtils::SetString(pElement, SAVESTATE_FIELD_THUMBNAIL, m_thumbnail); + + if (!xmlFile.SaveFile(path)) + { + CLog::Log(LOGERROR, "Failed to serialize savestate to %s: %s", path.c_str(), xmlFile.ErrorDesc()); + return false; + } + + return true; +} + +bool CSavestate::Deserialize(const std::string& path) +{ + Reset(); + + TiXmlDocument xmlFile; + if (!xmlFile.LoadFile(path)) + { + CLog::Log(LOGERROR, "Failed to open %s: %s", path.c_str(), xmlFile.ErrorDesc()); + return false; + } + + TiXmlElement* pElement = xmlFile.RootElement(); + if (!pElement || pElement->NoChildren() || pElement->ValueStr() != SAVESTATE_XML_ROOT) + { + CLog::Log(LOGERROR, "Can't find root <%s> tag", SAVESTATE_XML_ROOT); + return false; + } + + // Path + if (!XMLUtils::GetString(pElement, SAVESTATE_FIELD_PATH, m_path)) + { + CLog::Log(LOGERROR, "Savestate has no <%s> element", SAVESTATE_FIELD_PATH); + return false; + } + + // Type + std::string type; + if (!XMLUtils::GetString(pElement, SAVESTATE_FIELD_TYPE, type)) + { + CLog::Log(LOGERROR, "Savestate has no <%s> element", SAVESTATE_FIELD_TYPE); + return false; + } + m_type = CSavestateTranslator::TranslateType(type); + if (m_type == SAVETYPE::UNKNOWN) + { + CLog::Log(LOGERROR, "Invalid savestate type: %s", type.c_str()); + return false; + } + + // Slot + if (m_type == SAVETYPE::SLOT) + { + if (!XMLUtils::GetInt(pElement, SAVESTATE_FIELD_SLOT, m_slot)) + { + CLog::Log(LOGERROR, "Savestate has type \"%s\" but no <%s> element!", type.c_str(), SAVESTATE_FIELD_TYPE); + return false; + } + if (m_slot < 0) + { + CLog::Log(LOGERROR, "Invalid savestate slot: %d", m_slot); + return false; + } + } + + // Label (optional) + XMLUtils::GetString(pElement, SAVESTATE_FIELD_LABEL, m_label); + + // Size + long size; + if (!XMLUtils::GetLong(pElement, SAVESTATE_FIELD_SIZE, size)) + { + CLog::Log(LOGERROR, "Savestate has no <%s> element", SAVESTATE_FIELD_SIZE); + return false; + } + if (size < 0) + { + CLog::Log(LOGERROR, "Invalid savestate size: %ld", size); + return false; + } + m_size = size; + + // Game client + if (!XMLUtils::GetString(pElement, SAVESTATE_FIELD_GAMECLIENT, m_gameClient)) + { + CLog::Log(LOGERROR, "Savestate has no <%s> element", SAVESTATE_FIELD_GAMECLIENT); + return false; + } + + // Game path + if (!XMLUtils::GetString(pElement, SAVESTATE_FIELD_GAME_PATH, m_gamePath)) + { + CLog::Log(LOGERROR, "Savestate has no <%s> element", SAVESTATE_FIELD_GAME_PATH); + return false; + } + + // Game CRC + if (!XMLUtils::GetString(pElement, SAVESTATE_FIELD_GAME_CRC, m_gameCRC)) + { + CLog::Log(LOGERROR, "Savestate has no <%s> element", SAVESTATE_FIELD_GAME_CRC); + return false; + } + + // Playtime (frames) + long playtimeFrames; + if (!XMLUtils::GetLong(pElement, SAVESTATE_FIELD_FRAMES, playtimeFrames)) + { + CLog::Log(LOGERROR, "Savestate has no <%s> element", SAVESTATE_FIELD_FRAMES); + return false; + } + if (playtimeFrames < 0) + { + CLog::Log(LOGERROR, "Invalid savestate frame count: %ld", playtimeFrames); + return false; + } + m_size = playtimeFrames; + + // Playtime (wall clock) + float playtimeWallClock; + if (!XMLUtils::GetFloat(pElement, SAVESTATE_FIELD_WALLCLOCK, playtimeWallClock)) + { + CLog::Log(LOGERROR, "Savestate has no <%s> element", SAVESTATE_FIELD_WALLCLOCK); + return false; + } + m_playtimeWallClock = playtimeWallClock; + + // Timestamp + std::string timestamp; + if (!XMLUtils::GetString(pElement, SAVESTATE_FIELD_TIMESTAMP, timestamp)) + { + CLog::Log(LOGERROR, "Savestate has no <%s> element", SAVESTATE_FIELD_TIMESTAMP); + return false; + } + if (!m_timestamp.SetFromDBDateTime(timestamp)) + { + CLog::Log(LOGERROR, "Invalid savestate timestamp: %s", timestamp.c_str()); + return false; + } + + // Thumbnail + if (!XMLUtils::GetString(pElement, SAVESTATE_FIELD_THUMBNAIL, m_thumbnail)) + { + CLog::Log(LOGERROR, "Savestate has no <%s> element", SAVESTATE_FIELD_THUMBNAIL); + return false; + } + + return true; +} diff --git a/xbmc/games/addons/savestates/Savestate.h b/xbmc/games/addons/savestates/Savestate.h new file mode 100644 index 0000000000..7180a56df3 --- /dev/null +++ b/xbmc/games/addons/savestates/Savestate.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "XBDateTime.h" + +#include <stdint.h> +#include <string> + +class CVariant; + +namespace GAME +{ + enum class SAVETYPE + { + UNKNOWN = 0, + AUTO = 1, + SLOT = 2, + MANUAL = 3, + }; + + class CSavestate + { + public: + CSavestate() { Reset(); } + + virtual ~CSavestate() = default; + + void Reset(); + + const std::string& Path() const { return m_path; } + SAVETYPE Type() const { return m_type; } + int Slot() const { return m_slot; } + const std::string& Label() const { return m_label; } + size_t Size() const { return m_size; } + const std::string& GameClient() const { return m_gameClient; } + int DatabaseId() const { return m_databaseId; } + bool IsDatabaseObject() const { return m_databaseId != -1; } + const std::string& GamePath() const { return m_gamePath; } + const std::string& GameCRC() const { return m_gameCRC; } + uint64_t PlaytimeFrames() const { return m_playtimeFrames; } + double PlaytimeWallClock() const { return m_playtimeWallClock; } + const CDateTime& Timestamp() const { return m_timestamp; } + const std::string& Thumbnail() const { return m_thumbnail; } + + void SetPath(const std::string& path) { m_path = path; } + void SetType(SAVETYPE type) { m_type = type; } + void SetSlot(int slot) { m_slot = slot; } + void SetLabel(const std::string& label) { m_label = label; } + void SetSize(size_t size) { m_size = size; } + void SetGameClient(const std::string& gameClient) { m_gameClient = gameClient; } + void SetDatabaseId(int id) { m_databaseId = id; } + void SetGamePath(const std::string& gamePath) { m_gamePath = gamePath; } + void SetGameCRC(const std::string& crc) { m_gameCRC = crc; } + void SetPlaytimeFrames(uint64_t frames) { m_playtimeFrames = frames; } + void SetPlaytimeWallClock(double playtime) { m_playtimeWallClock = playtime; } + void SetTimestamp(const CDateTime& timestamp) { m_timestamp = timestamp; } + void SetThumbnail(const std::string& thumbnail) { m_thumbnail = thumbnail; } + + void Serialize(CVariant& value) const; + void Deserialize(const CVariant& value); + + bool Serialize(const std::string& path) const; + bool Deserialize(const std::string& path); + + private: + // Savestate properties + std::string m_path; + SAVETYPE m_type; + int m_slot; // -1 for no slot + std::string m_label; + size_t m_size; + std::string m_gameClient; + + // Database properties + int m_databaseId; + + // Gameplay properties + std::string m_gamePath; + std::string m_gameCRC; + uint64_t m_playtimeFrames; + double m_playtimeWallClock; // seconds + CDateTime m_timestamp; + std::string m_thumbnail; + }; +} diff --git a/xbmc/games/addons/savestates/SavestateDatabase.cpp b/xbmc/games/addons/savestates/SavestateDatabase.cpp new file mode 100644 index 0000000000..faebda2b87 --- /dev/null +++ b/xbmc/games/addons/savestates/SavestateDatabase.cpp @@ -0,0 +1,110 @@ + /* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "SavestateDatabase.h" +#include "Savestate.h" +#include "SavestateDefines.h" +#include "SavestateUtils.h" +#include "addons/AddonManager.h" +#include "dbwrappers/dataset.h" +#include "filesystem/File.h" +#include "games/GameTypes.h" +#include "games/tags/GameInfoTag.h" +#include "settings/AdvancedSettings.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "FileItem.h" + +#include "utils/log.h" + +using namespace GAME; + +#define SAVESTATE_OBJECT "savestate" + +CSavestateDatabase::CSavestateDatabase() +{ +} + +bool CSavestateDatabase::AddSavestate(const CSavestate& save) +{ + //! @todo + return false; +} + +bool CSavestateDatabase::GetSavestate(const std::string& path, CSavestate& save) +{ + //! @todo + return false; +} + +bool CSavestateDatabase::GetSavestatesNav(CFileItemList& items, const std::string& gamePath, const std::string& gameClient /* = "" */) +{ + //! @todo + return false; +} + +bool CSavestateDatabase::RenameSavestate(const std::string& path, const std::string& label) +{ + //! @todo + return false; +} + +bool CSavestateDatabase::DeleteSavestate(const std::string& path) +{ + //! @todo + return false; +} + +bool CSavestateDatabase::ClearSavestatesOfGame(const std::string& gamePath, const std::string& gameClient /* = "" */) +{ + //! @todo + return false; +} + +CFileItem* CSavestateDatabase::CreateFileItem(const CVariant& object) const +{ + using namespace ADDON; + + CSavestate save; + save.Deserialize(object); + CFileItem* item = new CFileItem(save.Label()); + + item->SetPath(save.Path()); + if (!save.Thumbnail().empty()) + item->SetArt("thumb", save.Thumbnail()); + else + { + AddonPtr addon; + if (CAddonMgr::GetInstance().GetAddon(save.GameClient(), addon, ADDON_GAMEDLL)) + item->SetArt("thumb", addon->Icon()); + } + + // Use the slot number as the second label + if (save.Type() == SAVETYPE::SLOT) + item->SetLabel2(StringUtils::Format("%u", save.Slot())); + + item->m_dateTime = save.Timestamp(); + item->SetProperty(FILEITEM_PROPERTY_SAVESTATE_DURATION, static_cast<uint64_t>(save.PlaytimeWallClock())); + item->GetGameInfoTag()->SetGameClient(save.GameClient()); + item->m_dwSize = save.Size(); + item->m_bIsFolder = false; + + return item; +} diff --git a/xbmc/games/addons/savestates/SavestateDatabase.h b/xbmc/games/addons/savestates/SavestateDatabase.h new file mode 100644 index 0000000000..24234611ca --- /dev/null +++ b/xbmc/games/addons/savestates/SavestateDatabase.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include <string> + +#define SAVESTATES_DATABASE_NAME "Savestates" + +class CFileItem; +class CFileItemList; +class CVariant; + +namespace GAME +{ + class CSavestate; + + class CSavestateDatabase + { + public: + CSavestateDatabase(); + virtual ~CSavestateDatabase() = default; + + bool AddSavestate(const CSavestate& save); + + bool GetSavestate(const std::string& path, CSavestate& save); + + bool GetSavestatesNav(CFileItemList& items, const std::string& gamePath, const std::string& gameClient = ""); + + bool RenameSavestate(const std::string& path, const std::string& label); + + bool DeleteSavestate(const std::string& path); + + bool ClearSavestatesOfGame(const std::string& gamePath, const std::string& gameClient = ""); + + private: + CFileItem* CreateFileItem(const CVariant& object) const; + }; +} diff --git a/xbmc/games/addons/savestates/SavestateDefines.h b/xbmc/games/addons/savestates/SavestateDefines.h new file mode 100644 index 0000000000..3f739db221 --- /dev/null +++ b/xbmc/games/addons/savestates/SavestateDefines.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#define SAVESTATE_XML_ROOT "savestate" + +#define SAVESTATE_FIELD_PATH "path" +#define SAVESTATE_FIELD_TYPE "type" +#define SAVESTATE_FIELD_SLOT "slot" +#define SAVESTATE_FIELD_LABEL "label" +#define SAVESTATE_FIELD_SIZE "size" +#define SAVESTATE_FIELD_GAMECLIENT "gameclient" +#define SAVESTATE_FIELD_DB_ID "databaseid" +#define SAVESTATE_FIELD_GAME_PATH "gamepath" +#define SAVESTATE_FIELD_GAME_CRC "gamecrc" +#define SAVESTATE_FIELD_FRAMES "frames" +#define SAVESTATE_FIELD_WALLCLOCK "wallclock" +#define SAVESTATE_FIELD_TIMESTAMP "timestamp" +#define SAVESTATE_FIELD_THUMBNAIL "thumbnail" + +#define SAVESTATE_TYPE_UNKNOWN "unknown" +#define SAVESTATE_TYPE_AUTO "auto" +#define SAVESTATE_TYPE_SLOT "slot" +#define SAVESTATE_TYPE_MANUAL "manual" + +#define FILEITEM_PROPERTY_SAVESTATE_DURATION "duration" diff --git a/xbmc/games/addons/savestates/SavestateReader.cpp b/xbmc/games/addons/savestates/SavestateReader.cpp new file mode 100644 index 0000000000..32cdaffea1 --- /dev/null +++ b/xbmc/games/addons/savestates/SavestateReader.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "SavestateReader.h" +#include "filesystem/File.h" +#include "games/addons/GameClient.h" +#include "IMemoryStream.h" + +using namespace GAME; + +CSavestateReader::CSavestateReader() : + m_frameCount(0) +{ +} + +CSavestateReader::~CSavestateReader() +{ +} + +bool CSavestateReader::Initialize(const std::string& path, const CGameClient* gameClient) +{ + bool bSuccess = false; + + CLog::Log(LOGDEBUG, "Loading savestate from %s", path.c_str()); + + if (m_db.GetSavestate(path, m_savestate)) + { + // Sanity checks + if (m_savestate.GameClient() == gameClient->ID()) + bSuccess = true; + else + CLog::Log(LOGDEBUG, "Savestate game client %s doesn't match active %s", m_savestate.GameClient().c_str(), gameClient->ID().c_str()); + } + else + CLog::Log(LOGERROR, "Failed to query savestate %s", path.c_str()); + + return bSuccess; +} + +bool CSavestateReader::ReadSave(IMemoryStream* memoryStream) +{ + using namespace XFILE; + + bool bSuccess = false; + + CFile file; + if (file.Open(m_savestate.Path())) + { + ssize_t read = file.Read(memoryStream->BeginFrame(), memoryStream->FrameSize()); + if (read == static_cast<ssize_t>(memoryStream->FrameSize())) + { + memoryStream->SubmitFrame(); + m_frameCount = m_savestate.PlaytimeFrames(); + bSuccess = true; + } + } + + if (!bSuccess) + CLog::Log(LOGERROR, "Failed to read savestate %s", m_savestate.Path().c_str()); + + return bSuccess; +} diff --git a/xbmc/games/addons/savestates/SavestateReader.h b/xbmc/games/addons/savestates/SavestateReader.h new file mode 100644 index 0000000000..5494c803ca --- /dev/null +++ b/xbmc/games/addons/savestates/SavestateReader.h @@ -0,0 +1,48 @@ +/* +* Copyright (C) 2016 Team Kodi +* http://kodi.tv +* +* This Program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see +* <http://www.gnu.org/licenses/>. +* +*/ +#pragma once + +#include "Savestate.h" +#include "SavestateDatabase.h" + +#include <stdint.h> +#include <string> + +namespace GAME +{ + class CGameClient; + class IMemoryStream; + + class CSavestateReader + { + public: + CSavestateReader(); + ~CSavestateReader(); + + bool Initialize(const std::string& path, const CGameClient* gameClient); + bool ReadSave(IMemoryStream* memoryStream); + uint64_t GetFrameCount(void) const { return m_frameCount; } + + private: + CSavestate m_savestate; + CSavestateDatabase m_db; + uint64_t m_frameCount; + }; +} diff --git a/xbmc/games/addons/savestates/SavestateTranslator.cpp b/xbmc/games/addons/savestates/SavestateTranslator.cpp new file mode 100644 index 0000000000..f1dba1bebe --- /dev/null +++ b/xbmc/games/addons/savestates/SavestateTranslator.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "SavestateTranslator.h" +#include "SavestateDefines.h" + +using namespace GAME; + +SAVETYPE CSavestateTranslator::TranslateType(const std::string& type) +{ + if (type == SAVESTATE_TYPE_AUTO) return SAVETYPE::AUTO; + else if (type == SAVESTATE_TYPE_SLOT) return SAVETYPE::SLOT; + else if (type == SAVESTATE_TYPE_MANUAL) return SAVETYPE::MANUAL; + + return SAVETYPE::UNKNOWN; +} + +std::string CSavestateTranslator::TranslateType(const SAVETYPE& type) +{ + switch (type) + { + case SAVETYPE::AUTO: return SAVESTATE_TYPE_AUTO; + case SAVETYPE::SLOT: return SAVESTATE_TYPE_SLOT; + case SAVETYPE::MANUAL: return SAVESTATE_TYPE_MANUAL; + default: + break; + } + return SAVESTATE_TYPE_UNKNOWN; +} diff --git a/xbmc/games/addons/savestates/SavestateTranslator.h b/xbmc/games/addons/savestates/SavestateTranslator.h new file mode 100644 index 0000000000..67f8314f14 --- /dev/null +++ b/xbmc/games/addons/savestates/SavestateTranslator.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "Savestate.h" + +#include <string> + +namespace GAME +{ + class CSavestateTranslator + { + public: + static SAVETYPE TranslateType(const std::string& type); + static std::string TranslateType(const SAVETYPE& type); + }; +} diff --git a/xbmc/games/addons/savestates/SavestateUtils.cpp b/xbmc/games/addons/savestates/SavestateUtils.cpp new file mode 100644 index 0000000000..4067692bae --- /dev/null +++ b/xbmc/games/addons/savestates/SavestateUtils.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2012-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "SavestateUtils.h" +#include "Savestate.h" +#include "filesystem/Directory.h" +#include "filesystem/File.h" +#include "profiles/ProfilesManager.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" + +#include <algorithm> +#include <cctype> +#include <cstring> +#include <sstream> + +#define SAVESTATE_EXTENSION ".sav" +#define SAVESTATE_AUTO_PREFIX "auto_" +#define SAVESTATE_SLOT_PREFIX "slot%d_" +#define SAVESTATE_MANUAL_PREFIX "save_" + +using namespace GAME; + +namespace +{ + void make_safe_path(std::string& path) + { + // Replace unsafe characters with "_" + std::transform(path.begin(), path.end(), path.begin(), + [](char c) + { + if (std::isalnum(c)) + return c; + + switch (c) + { + case '-': + case '.': + case '_': + case '[': + case ']': + case '(': + case ')': + case '!': + return c; + default: + break; + } + + return '_'; + }); + + // Combine successive runs of underscores + path.erase(std::unique(path.begin(), path.end(), + [](char a, char b) + { + return a == '_' && b == '_'; + }), path.end()); + + // Limit folderName to a sane number of characters + if (path.length() > 40) + path.erase(path.begin() + 40, path.end()); + + // Trim trailing underscores + StringUtils::TrimRight(path, "_"); + } +} + +std::string CSavestateUtils::MakePath(const CSavestate& save) +{ + using namespace XFILE; + + if (save.GameClient().empty()) + return ""; + + // Build path + std::string savePath = CProfilesManager::GetInstance().GetSavestatesFolder(); + + // Append game client + savePath = URIUtils::AddFileToFolder(savePath, save.GameClient()); + + // Generate a folder name based on game path and CRC + std::string folderName = URIUtils::GetFileName(save.GamePath()) + "_" + save.GameCRC(); + + make_safe_path(folderName); + + savePath = URIUtils::AddFileToFolder(savePath, folderName); + + if (!CDirectory::Exists(savePath)) + CDirectory::Create(savePath); + + // Generate a filename based on type + std::string filename; + switch (save.Type()) + { + case SAVETYPE::AUTO: + filename = SAVESTATE_AUTO_PREFIX + save.Timestamp().GetAsDBDateTime(); + break; + case SAVETYPE::SLOT: + filename = StringUtils::Format(SAVESTATE_SLOT_PREFIX, save.Slot()) + save.Timestamp().GetAsDBDateTime(); + break; + case SAVETYPE::MANUAL: + filename = SAVESTATE_MANUAL_PREFIX + save.Timestamp().GetAsDBDateTime(); + break; + default: + return ""; // Invalid savestate, bail out + } + + make_safe_path(filename); + + filename += SAVESTATE_EXTENSION; + + savePath = URIUtils::AddFileToFolder(savePath, filename); + + for (unsigned int i = 1; i <= 9; i++) + { + if (!CFile::Exists(savePath)) + break; + + if (i != 1) + savePath.erase(savePath.end() - 1, savePath.end()); + + savePath += StringUtils::Format("%u", i); + } + + return savePath; +} + +std::string CSavestateUtils::MakeThumbPath(const std::string& savePath) +{ + return URIUtils::ReplaceExtension(savePath, ".jpg"); +} diff --git a/xbmc/games/addons/savestates/SavestateUtils.h b/xbmc/games/addons/savestates/SavestateUtils.h new file mode 100644 index 0000000000..e3f705e980 --- /dev/null +++ b/xbmc/games/addons/savestates/SavestateUtils.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include <string> + +namespace GAME +{ + class CSavestate; + + class CSavestateUtils + { + public: + /*! + * \brief Calculate a path for the specified savestate + * + * Path to savestate is derived from game client and game CRC. Returns empty + * if either of these is unknown. Format is + * + * Autosave (hex is game CRC): + * special://savegames/gameclient.id/feba62c2.sav + * + * Save type slot (digit after the underscore is slot 1-9): + * special://savegames/gameclient.id/feba62c2_1.sav + * + * Save type label (hex after the underscore is CRC of the label): + * special://savegames/gameclient.id/feba62c2_8dc22669.sav + */ + static std::string MakePath(const CSavestate& save); + + /*! + * \brief Calculate the thumbnail path for the specified savestate + * + * This is the savestate path with a different extension + */ + static std::string MakeThumbPath(const std::string& savePath); + }; +} diff --git a/xbmc/games/addons/savestates/SavestateWriter.cpp b/xbmc/games/addons/savestates/SavestateWriter.cpp new file mode 100644 index 0000000000..8d90027a7e --- /dev/null +++ b/xbmc/games/addons/savestates/SavestateWriter.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "SavestateWriter.h" +#include "filesystem/File.h" +#include "games/addons/GameClient.h" +#include "IMemoryStream.h" +#include "games/addons/savestates/SavestateUtils.h" +#include "pictures/Picture.h" +#include "settings/AdvancedSettings.h" +#include "utils/Crc32.h" +#include "utils/StringUtils.h" +#include "Application.h" +#include "XBDateTime.h" + +using namespace GAME; + +CSavestateWriter::CSavestateWriter() : + m_fps(0.0) +{ +} + +CSavestateWriter::~CSavestateWriter() +{ +} + +bool CSavestateWriter::Initialize(const CGameClient* gameClient, uint64_t frameHistoryCount) +{ + m_savestate.Reset(); + m_fps = 0.0; + + m_fps = gameClient->Timing().GetFrameRate(); + + CDateTime now = CDateTime::GetCurrentDateTime(); + std::string label = now.GetAsLocalizedDateTime(); + + m_savestate.SetType(SAVETYPE::MANUAL); + m_savestate.SetLabel(label); + m_savestate.SetGameClient(gameClient->ID()); + m_savestate.SetGamePath(gameClient->GetGamePath()); + m_savestate.SetTimestamp(now); + m_savestate.SetPlaytimeFrames(frameHistoryCount); + m_savestate.SetPlaytimeWallClock(frameHistoryCount / m_fps); //! @todo Accumulate playtime instead of deriving it + + //! @todo Get CRC from game data instead of filename + Crc32 crc; + crc.Compute(gameClient->GetGamePath()); + m_savestate.SetGameCRC(StringUtils::Format("%08x", (unsigned __int32)crc)); + + m_savestate.SetPath(CSavestateUtils::MakePath(m_savestate)); + if (m_savestate.Path().empty()) + CLog::Log(LOGDEBUG, "Failed to calculate savestate path"); + + if (m_fps == 0.0) + return false; // Sanity check + + return !m_savestate.Path().empty(); +} + +bool CSavestateWriter::WriteSave(IMemoryStream* memoryStream) +{ + using namespace XFILE; + + if (memoryStream->CurrentFrame() == nullptr) + return false; + + m_savestate.SetSize(memoryStream->FrameSize()); + + CLog::Log(LOGDEBUG, "Saving savestate to %s", m_savestate.Path().c_str()); + + bool bSuccess = false; + + CFile file; + if (file.OpenForWrite(m_savestate.Path())) + { + ssize_t written = file.Write(memoryStream->CurrentFrame(), memoryStream->FrameSize()); + bSuccess = (written == static_cast<ssize_t>(memoryStream->FrameSize())); + } + + if (!bSuccess) + CLog::Log(LOGERROR, "Failed to write savestate to %s", m_savestate.Path().c_str()); + + return bSuccess; +} + +void CSavestateWriter::WriteThumb() +{ + if (g_advancedSettings.m_imageRes == 0) + return; // Sanity check + + std::string thumbPath = CSavestateUtils::MakeThumbPath(m_savestate.Path()); + CLog::Log(LOGDEBUG, "Saving savestate thumbnail to %s", thumbPath.c_str()); + + // Calculate width and height + float aspectRatio = g_application.m_pPlayer->GetRenderAspectRatio(); + if (aspectRatio <= 0.0f) + aspectRatio = 1.0f; + + unsigned int width; + unsigned int height; + if (aspectRatio >= 1.0f) + { + width = g_advancedSettings.m_imageRes; + height = static_cast<unsigned int>(g_advancedSettings.m_imageRes / aspectRatio); + } + else + { + width = static_cast<unsigned int>(g_advancedSettings.m_imageRes * aspectRatio); + height = g_advancedSettings.m_imageRes; + } + + // Allocate pixels + uint8_t* pixels = new uint8_t[height * width * 4]; + + // Capture picture + unsigned int captureId = g_application.m_pPlayer->RenderCaptureAlloc(); + g_application.m_pPlayer->RenderCapture(captureId, width, height, CAPTUREFLAG_IMMEDIATELY); + bool hasImage = g_application.m_pPlayer->RenderCaptureGetPixels(captureId, 1000, pixels, height * width * 4); + + // Save picture + if (hasImage) + { + if (CPicture::CreateThumbnailFromSurface(pixels, width, height, width * 4, thumbPath)) + m_savestate.SetThumbnail(thumbPath); + else + CLog::Log(LOGERROR, "Failed to save thumbnail to %s", thumbPath.c_str()); + + g_application.m_pPlayer->RenderCaptureRelease(captureId); + } + else + CLog::Log(LOGERROR, "Failed to capture thumbnail"); + + // Free pixels + delete[] pixels; +} + +bool CSavestateWriter::CommitToDatabase() +{ + bool bSuccess = m_db.AddSavestate(m_savestate); + + if (!bSuccess) + CLog::Log(LOGERROR, "Failed to write savestate to database: %s", m_savestate.Path().c_str()); + + return bSuccess; +} + +void CSavestateWriter::CleanUpTransaction() +{ + using namespace XFILE; + + CFile::Delete(m_savestate.Path()); + if (CFile::Exists(m_savestate.Thumbnail())) + CFile::Delete(m_savestate.Thumbnail()); +} diff --git a/xbmc/games/addons/savestates/SavestateWriter.h b/xbmc/games/addons/savestates/SavestateWriter.h new file mode 100644 index 0000000000..c60a74d7c8 --- /dev/null +++ b/xbmc/games/addons/savestates/SavestateWriter.h @@ -0,0 +1,51 @@ +/* +* Copyright (C) 2016 Team Kodi +* http://kodi.tv +* +* This Program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see +* <http://www.gnu.org/licenses/>. +* +*/ +#pragma once + +#include "Savestate.h" +#include "SavestateDatabase.h" + +#include <stdint.h> +#include <string> + +namespace GAME +{ + class CGameClient; + class IMemoryStream; + + class CSavestateWriter + { + public: + CSavestateWriter(); + ~CSavestateWriter(); + + bool Initialize(const CGameClient* gameClient, uint64_t frameHistoryCount); + bool WriteSave(IMemoryStream* memoryStream); + void WriteThumb(); + bool CommitToDatabase(); + void CleanUpTransaction(); + const std::string& GetPath() const { return m_savestate.Path(); } + + private: + CSavestate m_savestate; + double m_fps; //! @todo + CSavestateDatabase m_db; + }; +} diff --git a/xbmc/games/controllers/ControllerDefinitions.h b/xbmc/games/controllers/ControllerDefinitions.h index 8ade354737..bcb1639e43 100644 --- a/xbmc/games/controllers/ControllerDefinitions.h +++ b/xbmc/games/controllers/ControllerDefinitions.h @@ -27,6 +27,7 @@ #define LAYOUT_XML_ELM_ANALOG_STICK "analogstick" #define LAYOUT_XML_ELM_ACCELEROMETER "accelerometer" #define LAYOUT_XML_ELM_MOTOR "motor" +#define LAYOUT_XML_ELM_RELPOINTER "relpointer" #define LAYOUT_XML_ATTR_LAYOUT_LABEL "label" #define LAYOUT_XML_ATTR_LAYOUT_IMAGE "image" diff --git a/xbmc/games/controllers/ControllerTranslator.cpp b/xbmc/games/controllers/ControllerTranslator.cpp index 692960e720..0b0867c1a2 100644 --- a/xbmc/games/controllers/ControllerTranslator.cpp +++ b/xbmc/games/controllers/ControllerTranslator.cpp @@ -32,6 +32,7 @@ const char* CControllerTranslator::TranslateFeatureType(FEATURE_TYPE type) case FEATURE_TYPE::ANALOG_STICK: return LAYOUT_XML_ELM_ANALOG_STICK; case FEATURE_TYPE::ACCELEROMETER: return LAYOUT_XML_ELM_ACCELEROMETER; case FEATURE_TYPE::MOTOR: return LAYOUT_XML_ELM_MOTOR; + case FEATURE_TYPE::RELPOINTER: return LAYOUT_XML_ELM_RELPOINTER; default: break; } @@ -44,6 +45,7 @@ FEATURE_TYPE CControllerTranslator::TranslateFeatureType(const std::string& strT if (strType == LAYOUT_XML_ELM_ANALOG_STICK) return FEATURE_TYPE::ANALOG_STICK; if (strType == LAYOUT_XML_ELM_ACCELEROMETER) return FEATURE_TYPE::ACCELEROMETER; if (strType == LAYOUT_XML_ELM_MOTOR) return FEATURE_TYPE::MOTOR; + if (strType == LAYOUT_XML_ELM_RELPOINTER) return FEATURE_TYPE::RELPOINTER; return FEATURE_TYPE::UNKNOWN; } diff --git a/xbmc/games/controllers/windows/GUIConfigurationWizard.cpp b/xbmc/games/controllers/windows/GUIConfigurationWizard.cpp index f559db7675..525f83ca63 100644 --- a/xbmc/games/controllers/windows/GUIConfigurationWizard.cpp +++ b/xbmc/games/controllers/windows/GUIConfigurationWizard.cpp @@ -278,6 +278,11 @@ bool CGUIConfigurationWizard::OnKeyPress(const CKey& key) return Abort(false); } +bool CGUIConfigurationWizard::OnButtonPress(const std::string& button) +{ + return Abort(false); +} + void CGUIConfigurationWizard::InstallHooks(void) { using namespace PERIPHERALS; @@ -288,12 +293,16 @@ void CGUIConfigurationWizard::InstallHooks(void) // If we're not using emulation, allow keyboard input to abort prompt if (!m_bEmulation) CInputManager::GetInstance().RegisterKeyboardHandler(this); + + CInputManager::GetInstance().RegisterMouseHandler(this); } void CGUIConfigurationWizard::RemoveHooks(void) { using namespace PERIPHERALS; + CInputManager::GetInstance().UnregisterMouseHandler(this); + if (!m_bEmulation) CInputManager::GetInstance().UnregisterKeyboardHandler(this); diff --git a/xbmc/games/controllers/windows/GUIConfigurationWizard.h b/xbmc/games/controllers/windows/GUIConfigurationWizard.h index 112d41735f..b37aa1a018 100644 --- a/xbmc/games/controllers/windows/GUIConfigurationWizard.h +++ b/xbmc/games/controllers/windows/GUIConfigurationWizard.h @@ -23,6 +23,7 @@ #include "input/joysticks/DriverPrimitive.h" #include "input/joysticks/IButtonMapper.h" #include "input/keyboard/IKeyboardHandler.h" +#include "input/mouse/IMouseInputHandler.h" #include "threads/CriticalSection.h" #include "threads/Event.h" #include "threads/Thread.h" @@ -37,6 +38,7 @@ namespace GAME class CGUIConfigurationWizard : public IConfigurationWizard, public JOYSTICK::IButtonMapper, public KEYBOARD::IKeyboardHandler, + public MOUSE::IMouseInputHandler, public Observer, protected CThread { @@ -64,6 +66,11 @@ namespace GAME virtual bool OnKeyPress(const CKey& key) override; virtual void OnKeyRelease(const CKey& key) override { } + // implementation of IMouseInputHandler + virtual bool OnMotion(const std::string& relpointer, int dx, int dy) override { return false; } + virtual bool OnButtonPress(const std::string& button) override; + virtual void OnButtonRelease(const std::string& button) override { } + // implementation of Observer virtual void Notify(const Observable& obs, const ObservableMessage msg) override; diff --git a/xbmc/games/dialogs/CMakeLists.txt b/xbmc/games/dialogs/CMakeLists.txt new file mode 100644 index 0000000000..c1f7b46c7a --- /dev/null +++ b/xbmc/games/dialogs/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SOURCES GUIDialogSelectGameClient.cpp +) + +set(HEADERS GUIDialogSelectGameClient.h +) + +core_add_library(gamedialogs) diff --git a/xbmc/games/dialogs/GUIDialogSelectGameClient.cpp b/xbmc/games/dialogs/GUIDialogSelectGameClient.cpp new file mode 100644 index 0000000000..4a5b1302df --- /dev/null +++ b/xbmc/games/dialogs/GUIDialogSelectGameClient.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GUIDialogSelectGameClient.h" +#include "addons/AddonInstaller.h" +#include "addons/AddonManager.h" +#include "addons/GUIWindowAddonBrowser.h" +#include "dialogs/GUIDialogContextMenu.h" +#include "games/addons/GameClient.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/WindowIDs.h" +#include "utils/log.h" + +using namespace GAME; + +bool CGUIDialogSelectGameClient::ShowAndGetGameClient(const GameClientVector& candidates, const GameClientVector& installable, GameClientPtr& gameClient) +{ + CLog::Log(LOGDEBUG, "Select game client dialog: Found %lu candidates", candidates.size()); + for (const auto& gameClient : candidates) + CLog::Log(LOGDEBUG, "Adding %s as a candidate", gameClient->ID().c_str()); + + if (!installable.empty()) + { + CLog::Log(LOGDEBUG, "Select game client dialog: Found %lu installable clients", installable.size()); + for (const auto& gameClient : installable) + CLog::Log(LOGDEBUG, "Adding %s as an installable client", gameClient->ID().c_str()); + } + + CContextButtons choiceButtons; + + // Add emulators + int i = 0; + for (const GameClientPtr& gameClient : candidates) + choiceButtons.Add(i++, gameClient->Name()); + + // Add button to install emulators + const int iInstallEmulator = i++; + if (!installable.empty()) + choiceButtons.Add(iInstallEmulator, 35253); // "Install emulator" + + // Add button to manage emulators + const int iAddonMgr = i++; + choiceButtons.Add(iAddonMgr, 35254); // "Manage emulators" + + // Do modal + int result = CGUIDialogContextMenu::ShowAndGetChoice(choiceButtons); + + if (0 <= result && result < static_cast<int>(candidates.size())) + { + // Handle emulator + gameClient = candidates[result]; + } + else if (result == iInstallEmulator) + { + // Install emulator + gameClient = InstallGameClient(installable); + } + else if (result == iAddonMgr) + { + // Go to add-on manager to manage emulators + ActivateAddonMgr(); + } + else + { + CLog::Log(LOGDEBUG, "Select game client dialog: User cancelled game client selection"); + } + + return gameClient.get() != nullptr; +} + +GameClientPtr CGUIDialogSelectGameClient::InstallGameClient(const GameClientVector& installable) +{ + using namespace ADDON; + + GameClientPtr gameClient; + + //! @todo Switch to add-on browser when more emulators have icons + /* + std::string chosenClientId; + if (CGUIWindowAddonBrowser::SelectAddonID(ADDON_GAMEDLL, chosenClientId, false, true, false, true, false) >= 0 && !chosenClientId.empty()) + { + CLog::Log(LOGDEBUG, "Select game client dialog: User installed %s", chosenClientId.c_str()); + AddonPtr addon; + if (CAddonMgr::GetInstance().GetAddon(chosenClientId, addon, ADDON_GAMEDLL)) + gameClient = std::dynamic_pointer_cast<CGameClient>(addon); + + if (!gameClient) + CLog::Log(LOGERROR, "Select game client dialog: Failed to get addon %s", chosenClientId.c_str()); + } + */ + + CContextButtons choiceButtons; + + // Add emulators + int i = 0; + for (const GameClientPtr& gameClient : installable) + choiceButtons.Add(i++, gameClient->Name()); + + // Add button to browser all emulators + const int iAddonBrowser = i++; + choiceButtons.Add(iAddonBrowser, 35255); // "Browse all emulators" + + // Do modal + int result = CGUIDialogContextMenu::ShowAndGetChoice(choiceButtons); + + if (0 <= result && result < static_cast<int>(installable.size())) + { + std::string gameClientId = installable[result]->ID(); + CLog::Log(LOGDEBUG, "Select game client dialog: Installing %s", gameClientId.c_str()); + AddonPtr installedAddon; + if (CAddonInstaller::GetInstance().InstallModal(gameClientId, installedAddon, false)) + { + CLog::Log(LOGDEBUG, "Select game client dialog: Successfully installed %s", installedAddon->ID().c_str()); + + // if the addon is disabled we need to enable it + if (CAddonMgr::GetInstance().IsAddonDisabled(installedAddon->ID())) + CAddonMgr::GetInstance().EnableAddon(installedAddon->ID()); + + gameClient = std::dynamic_pointer_cast<CGameClient>(installedAddon); + } + else + { + CLog::Log(LOGERROR, "Select game client dialog: Failed to install %s", gameClientId.c_str()); + } + } + else if (result == iAddonBrowser) + { + ActivateAddonBrowser(); + } + else + { + CLog::Log(LOGDEBUG, "Select game client dialog: User cancelled game client installation"); + } + + return gameClient; +} + +void CGUIDialogSelectGameClient::ActivateAddonMgr() +{ + CLog::Log(LOGDEBUG, "User chose to go to the add-on manager"); + std::vector<std::string> params; + params.push_back("addons://user/category.emulators"); + g_windowManager.ActivateWindow(WINDOW_ADDON_BROWSER, params); +} + +void CGUIDialogSelectGameClient::ActivateAddonBrowser() +{ + CLog::Log(LOGDEBUG, "User chose to go to the add-on browser"); + std::vector<std::string> params; + params.push_back("addons://all/category.emulators"); + g_windowManager.ActivateWindow(WINDOW_ADDON_BROWSER, params); +} diff --git a/xbmc/games/dialogs/GUIDialogSelectGameClient.h b/xbmc/games/dialogs/GUIDialogSelectGameClient.h new file mode 100644 index 0000000000..acad17b319 --- /dev/null +++ b/xbmc/games/dialogs/GUIDialogSelectGameClient.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "games/GameTypes.h" + +namespace GAME +{ + class CGUIDialogSelectGameClient + { + public: + static bool ShowAndGetGameClient(const GameClientVector& candidates, const GameClientVector& installable, GameClientPtr& gameClient); + + private: + static GameClientPtr InstallGameClient(const GameClientVector& installable); + + /*! + * \brief Utility function to load the add-on manager for installed emulators + */ + static void ActivateAddonMgr(); + + /*! + * \brief Utility function to load the add-on manager for all emulators + */ + static void ActivateAddonBrowser(); + }; +} diff --git a/xbmc/games/dialogs/Makefile b/xbmc/games/dialogs/Makefile new file mode 100644 index 0000000000..b544a0ef56 --- /dev/null +++ b/xbmc/games/dialogs/Makefile @@ -0,0 +1,6 @@ +SRCS=GUIDialogSelectGameClient.cpp \ + +LIB=gamedialogs.a + +include ../../../Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) diff --git a/xbmc/games/ports/CMakeLists.txt b/xbmc/games/ports/CMakeLists.txt new file mode 100644 index 0000000000..16b3867674 --- /dev/null +++ b/xbmc/games/ports/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SOURCES PortManager.cpp + PortMapper.cpp) + +set(HEADERS PortManager.h + PortMapper.h) + +core_add_library(gameports) diff --git a/xbmc/games/ports/Makefile b/xbmc/games/ports/Makefile new file mode 100644 index 0000000000..4f1829ed69 --- /dev/null +++ b/xbmc/games/ports/Makefile @@ -0,0 +1,7 @@ +SRCS=PortManager.cpp \ + PortMapper.cpp \ + +LIB=gameports.a + +include ../../../Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) diff --git a/xbmc/games/ports/PortManager.cpp b/xbmc/games/ports/PortManager.cpp new file mode 100644 index 0000000000..5eafc923f9 --- /dev/null +++ b/xbmc/games/ports/PortManager.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2015-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PortManager.h" +#include "peripherals/devices/Peripheral.h" +#include "peripherals/devices/PeripheralJoystick.h" +#include "peripherals/devices/PeripheralJoystickEmulation.h" +#include "threads/SingleLock.h" + +#include <algorithm> + +using namespace GAME; +using namespace JOYSTICK; +using namespace PERIPHERALS; + +// --- GetRequestedPort() ----------------------------------------------------- + +namespace GAME +{ + int GetRequestedPort(const PERIPHERALS::PeripheralPtr& device) + { + if (device->Type() == PERIPHERAL_JOYSTICK) + return std::static_pointer_cast<CPeripheralJoystick>(device)->RequestedPort(); + return JOYSTICK_PORT_UNKNOWN; + } +} + +// --- CPortManager ----------------------------------------------------------- + +CPortManager& CPortManager::GetInstance() +{ + static CPortManager instance; + return instance; +} + +void CPortManager::OpenPort(IInputHandler* handler, + unsigned int port, + PERIPHERALS::PeripheralType requiredType /* = PERIPHERALS::PERIPHERAL_UNKNOWN) */) +{ + CSingleLock lock(m_mutex); + + SPort newPort = { }; + newPort.handler = handler; + newPort.port = port; + newPort.requiredType = requiredType; + m_ports.push_back(newPort); + + SetChanged(); + NotifyObservers(ObservableMessagePortsChanged); +} + +void CPortManager::ClosePort(IInputHandler* handler) +{ + CSingleLock lock(m_mutex); + + m_ports.erase(std::remove_if(m_ports.begin(), m_ports.end(), + [handler](const SPort& port) + { + return port.handler == handler; + }), m_ports.end()); + + SetChanged(); + NotifyObservers(ObservableMessagePortsChanged); +} + +void CPortManager::MapDevices(const PeripheralVector& devices, + std::map<PeripheralPtr, IInputHandler*>& deviceToPortMap) +{ + CSingleLock lock(m_mutex); + + if (m_ports.empty()) + return; // Nothing to do + + // Clear all ports + for (SPort& port : m_ports) + port.device = nullptr; + + // Prioritize devices by several criteria + PeripheralVector devicesCopy = devices; + std::sort(devicesCopy.begin(), devicesCopy.end(), + [](const PeripheralPtr& lhs, const PeripheralPtr& rhs) + { + // Prioritize physical joysticks over emulated ones + if (lhs->Type() == PERIPHERAL_JOYSTICK && rhs->Type() != PERIPHERAL_JOYSTICK) + return true; + if (lhs->Type() != PERIPHERAL_JOYSTICK && rhs->Type() == PERIPHERAL_JOYSTICK) + return false; + + if (lhs->Type() == PERIPHERAL_JOYSTICK && rhs->Type() == PERIPHERAL_JOYSTICK) + { + std::shared_ptr<CPeripheralJoystick> i = std::static_pointer_cast<CPeripheralJoystick>(lhs); + std::shared_ptr<CPeripheralJoystick> j = std::static_pointer_cast<CPeripheralJoystick>(rhs); + + // Prioritize requested a port over no port requested + if (i->RequestedPort() != JOYSTICK_PORT_UNKNOWN && j->RequestedPort() == JOYSTICK_PORT_UNKNOWN) + return true; + if (i->RequestedPort() == JOYSTICK_PORT_UNKNOWN && j->RequestedPort() != JOYSTICK_PORT_UNKNOWN) + return false; + + // Sort joystick by requested port + return i->RequestedPort() < j->RequestedPort(); + } + + if (lhs->Type() == PERIPHERAL_JOYSTICK_EMULATION && rhs->Type() == PERIPHERAL_JOYSTICK_EMULATION) + { + std::shared_ptr<CPeripheralJoystickEmulation> i = std::static_pointer_cast<CPeripheralJoystickEmulation>(lhs); + std::shared_ptr<CPeripheralJoystickEmulation> j = std::static_pointer_cast<CPeripheralJoystickEmulation>(rhs); + + // Sort emulated joysticks by player number + return i->ControllerNumber() < j->ControllerNumber(); + } + + return false; + }); + + // Record mapped devices in output variable + for (auto& device : devicesCopy) + { + IInputHandler* handler = AssignToPort(device); + if (handler) + deviceToPortMap[device] = handler; + } +} + +IInputHandler* CPortManager::AssignToPort(const PeripheralPtr& device, bool checkPortNumber /* = true */) +{ + const int requestedPort = GetRequestedPort(device); + const bool bPortRequested = (requestedPort != JOYSTICK_PORT_UNKNOWN); + + for (SPort& port : m_ports) + { + // Skip occupied ports + if (port.device != nullptr) + continue; + + // If specified, check port numbers + if (checkPortNumber) + { + if (bPortRequested && requestedPort != static_cast<int>(port.port)) + continue; + } + + // If required, filter by type + const bool bTypeRequired = (port.requiredType != PERIPHERAL_UNKNOWN); + if (bTypeRequired && port.requiredType != device->Type()) + continue; + + // Success + port.device = device.get(); + return port.handler; + } + + // If joystick requested a port but wasn't mapped, try again without checking port numbers + if (checkPortNumber && bPortRequested) + return AssignToPort(device, false); + + return nullptr; +} diff --git a/xbmc/games/ports/PortManager.h b/xbmc/games/ports/PortManager.h new file mode 100644 index 0000000000..289c9ee935 --- /dev/null +++ b/xbmc/games/ports/PortManager.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2015-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "peripherals/PeripheralTypes.h" +#include "threads/CriticalSection.h" +#include "utils/Observer.h" + +#include <map> +#include <vector> + +namespace JOYSTICK { class IInputHandler; } +namespace PERIPHERALS { class CPeripheral; } + +namespace GAME +{ + /*! + * \brief Class to manage ports opened by game clients + */ + class CPortManager : public Observable + { + private: + CPortManager(void) = default; + + public: + static CPortManager& GetInstance(); + + /*! + * \brief Request a new port be opened with input on that port sent to the + * specified handler. + * + * \param handler The instance accepting all input delivered to the port + * \param port The port number belonging to the game client + * \param requiredType Used to restrict port to devices of only a certain type + */ + void OpenPort(JOYSTICK::IInputHandler* handler, + unsigned int port, + PERIPHERALS::PeripheralType requiredType = PERIPHERALS::PERIPHERAL_UNKNOWN); + + /*! + * \brief Close an opened port + * + * \param handler The handler used to open the port + */ + void ClosePort(JOYSTICK::IInputHandler* handler); + + /*! + * \brief Map a list of devices to the available ports + * + * \param devices The devices capable of providing input to the ports + * \param portMap The resulting map of devices to ports + * + * If there are more devices than open ports, multiple devices may be assigned + * to the same port. If a device requests a specific port, this function will + * attempt to honor that request. + */ + void MapDevices(const PERIPHERALS::PeripheralVector& devices, + std::map<PERIPHERALS::PeripheralPtr, JOYSTICK::IInputHandler*>& deviceToPortMap); + + private: + JOYSTICK::IInputHandler* AssignToPort(const PERIPHERALS::PeripheralPtr& device, bool checkPortNumber = true); + + struct SPort + { + JOYSTICK::IInputHandler* handler; // Input handler for this port + unsigned int port; // Port number belonging to the game client + PERIPHERALS::PeripheralType requiredType; + void* device; + }; + + std::vector<SPort> m_ports; + CCriticalSection m_mutex; + }; +} diff --git a/xbmc/games/ports/PortMapper.cpp b/xbmc/games/ports/PortMapper.cpp new file mode 100644 index 0000000000..2471c39a2b --- /dev/null +++ b/xbmc/games/ports/PortMapper.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2015-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PortMapper.h" +#include "PortManager.h" +#include "peripherals/devices/Peripheral.h" +#include "peripherals/Peripherals.h" + +using namespace GAME; +using namespace JOYSTICK; +using namespace PERIPHERALS; + +CPortMapper::CPortMapper() +{ + CPortManager::GetInstance().RegisterObserver(this); +} + +CPortMapper::~CPortMapper() +{ + CPortManager::GetInstance().UnregisterObserver(this); +} + +void CPortMapper::Notify(const Observable &obs, const ObservableMessage msg) +{ + switch (msg) + { + case ObservableMessagePeripheralsChanged: + case ObservableMessagePortsChanged: + ProcessPeripherals(); + break; + default: + break; + } +} + +void CPortMapper::ProcessPeripherals() +{ + auto& oldPortMap = m_portMap; + + PeripheralVector devices; + g_peripherals.GetPeripheralsWithFeature(devices, FEATURE_JOYSTICK); + + std::map<PeripheralPtr, IInputHandler*> newPortMap; + CPortManager::GetInstance().MapDevices(devices, newPortMap); + + for (auto& device : devices) + { + std::map<PeripheralPtr, IInputHandler*>::const_iterator itOld = oldPortMap.find(device); + std::map<PeripheralPtr, IInputHandler*>::const_iterator itNew = newPortMap.find(device); + + IInputHandler* oldHandler = itOld != oldPortMap.end() ? itOld->second : NULL; + IInputHandler* newHandler = itNew != newPortMap.end() ? itNew->second : NULL; + + if (oldHandler != newHandler) + { + // Unregister old handler + if (oldHandler != NULL) + device->UnregisterJoystickInputHandler(oldHandler); + + // Register new handler + if (newHandler != NULL) + device->RegisterJoystickInputHandler(newHandler); + } + } + + oldPortMap.swap(newPortMap); +} diff --git a/xbmc/games/ports/PortMapper.h b/xbmc/games/ports/PortMapper.h new file mode 100644 index 0000000000..60330039a2 --- /dev/null +++ b/xbmc/games/ports/PortMapper.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2015-2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "peripherals/PeripheralTypes.h" +#include "utils/Observer.h" + +#include <map> + +namespace JOYSTICK { class IInputHandler; } +namespace GAME +{ + class CPortMapper : public Observer + { + public: + CPortMapper(); + + virtual ~CPortMapper(); + + virtual void Notify(const Observable& obs, const ObservableMessage msg) override; + + private: + void ProcessPeripherals(); + + std::map<PERIPHERALS::PeripheralPtr, JOYSTICK::IInputHandler*> m_portMap; + }; +} diff --git a/xbmc/guilib/WindowIDs.h b/xbmc/guilib/WindowIDs.h index 943448ec36..abdd6188a3 100644 --- a/xbmc/guilib/WindowIDs.h +++ b/xbmc/guilib/WindowIDs.h @@ -145,6 +145,7 @@ #define WINDOW_FULLSCREEN_RADIO 10801 // virtual window for PVR radio specific keymaps with fallback to WINDOW_VISUALISATION #define WINDOW_DIALOG_GAME_CONTROLLERS 10820 +#define WINDOW_GAMES 10821 //#define WINDOW_VIRTUAL_KEYBOARD 11000 // WINDOW_ID's from 11100 to 11199 reserved for Skins @@ -163,6 +164,8 @@ #define WINDOW_VIDEO_MENU 12902 #define WINDOW_VIDEO_TIME_SEEK 12905 // virtual window for time seeking during fullscreen video +#define WINDOW_FULLSCREEN_GAME 12906 + #define WINDOW_SPLASH 12997 // splash window #define WINDOW_START 12998 // first window to load #define WINDOW_STARTUP_ANIM 12999 // for startup animations diff --git a/xbmc/input/InputManager.cpp b/xbmc/input/InputManager.cpp index 80c89f0283..2ceebed27a 100644 --- a/xbmc/input/InputManager.cpp +++ b/xbmc/input/InputManager.cpp @@ -23,6 +23,10 @@ #include "Application.h" #include "InputManager.h" #include "input/keyboard/IKeyboardHandler.h" +#include "input/mouse/generic/MouseInputHandling.h" +#include "input/mouse/IMouseDriverHandler.h" +#include "input/mouse/MouseWindowingButtonMap.h" +#include "input/keyboard/KeyboardEasterEgg.h" #include "input/Key.h" #include "messaging/ApplicationMessenger.h" #include "guilib/Geometry.h" @@ -68,6 +72,18 @@ using EVENTSERVER::CEventServer; using namespace KODI::MESSAGING; using PERIPHERALS::CPeripherals; +CInputManager::CInputManager() : + m_mouseButtonMap(new MOUSE::CMouseWindowingButtonMap), + m_keyboardEasterEgg(new KEYBOARD::CKeyboardEasterEgg) +{ + RegisterKeyboardHandler(m_keyboardEasterEgg.get()); +} + +CInputManager::~CInputManager() +{ + UnregisterKeyboardHandler(m_keyboardEasterEgg.get()); +} + CInputManager& CInputManager::GetInstance() { static CInputManager inputManager; @@ -382,9 +398,40 @@ bool CInputManager::OnEvent(XBMC_Event& newEvent) case XBMC_MOUSEBUTTONDOWN: case XBMC_MOUSEBUTTONUP: case XBMC_MOUSEMOTION: - m_Mouse.HandleEvent(newEvent); - ProcessMouse(g_windowManager.GetActiveWindowID()); + { + bool handled = false; + + for (auto it = m_mouseHandlers.begin(); it != m_mouseHandlers.end(); ++it) + { + if (newEvent.type == XBMC_MOUSEMOTION) + { + if (it->driverHandler->OnPosition(newEvent.motion.x, newEvent.motion.y)) + handled = true; + } + else + { + if (newEvent.button.type == XBMC_MOUSEBUTTONDOWN) + { + if (it->driverHandler->OnButtonPress(newEvent.button.button)) + handled = true; + } + else if (newEvent.button.type == XBMC_MOUSEBUTTONUP) + { + it->driverHandler->OnButtonRelease(newEvent.button.button); + } + } + + if (handled) + break; + } + + if (!handled) + { + m_Mouse.HandleEvent(newEvent); + ProcessMouse(g_windowManager.GetActiveWindowID()); + } break; + } case XBMC_TOUCH: { if (newEvent.touch.action == ACTION_TOUCH_TAP) @@ -787,3 +834,30 @@ void CInputManager::UnregisterKeyboardHandler(KEYBOARD::IKeyboardHandler* handle { m_keyboardHandlers.erase(std::remove(m_keyboardHandlers.begin(), m_keyboardHandlers.end(), handler), m_keyboardHandlers.end()); } + +std::string CInputManager::RegisterMouseHandler(MOUSE::IMouseInputHandler* handler) +{ + auto it = std::find_if(m_mouseHandlers.begin(), m_mouseHandlers.end(), + [handler](const MouseHandlerHandle& element) + { + return element.inputHandler == handler; + }); + + if (it == m_mouseHandlers.end()) + { + std::unique_ptr<MOUSE::IMouseDriverHandler> driverHandler(new MOUSE::CMouseInputHandling(handler, m_mouseButtonMap.get())); + MouseHandlerHandle handle = { handler, std::move(driverHandler) }; + m_mouseHandlers.insert(m_mouseHandlers.begin(), std::move(handle)); + } + + return m_mouseButtonMap->ControllerID(); +} + +void CInputManager::UnregisterMouseHandler(MOUSE::IMouseInputHandler* handler) +{ + m_mouseHandlers.erase(std::remove_if(m_mouseHandlers.begin(), m_mouseHandlers.end(), + [handler](const MouseHandlerHandle& handle) + { + return handle.inputHandler == handler; + }), m_mouseHandlers.end()); +} diff --git a/xbmc/input/InputManager.h b/xbmc/input/InputManager.h index 393565726c..d2767722a2 100644 --- a/xbmc/input/InputManager.h +++ b/xbmc/input/InputManager.h @@ -20,6 +20,7 @@ */ #include <map> +#include <memory> #include <string> #include <vector> @@ -43,13 +44,33 @@ namespace KEYBOARD class IKeyboardHandler; } +namespace MOUSE +{ + class IMouseButtonMap; + class IMouseDriverHandler; + class IMouseInputHandler; +} + +/// \addtogroup input +/// \{ + +/*! + * \ingroup input keyboard mouse touch joystick + * \brief Main input processing class. + * + * This class consolidates all input generated from different sources such as + * mouse, keyboard, joystick or touch (in \ref OnEvent). + * + * \copydoc keyboard + * \copydoc mouse + */ class CInputManager : public ISettingCallback { private: - CInputManager() { } + CInputManager(); CInputManager(const CInputManager&); CInputManager const& operator=(CInputManager const&); - virtual ~CInputManager() { }; + virtual ~CInputManager(); public: /*! \brief static method to get the current instance of the class. Creates a new instance the first time it's called. @@ -219,9 +240,32 @@ public: virtual void OnSettingChanged(const CSetting *setting) override; + /*! \brief Registers a handler to be called on keyboard input (e.g a game client). + * + * \param handler The handler to call on keyboard input. + */ void RegisterKeyboardHandler(KEYBOARD::IKeyboardHandler* handler); + + /*! \brief Unregisters handler from keyboard input. + * + * \param[in] handler The handler to unregister from keyboard input. + */ void UnregisterKeyboardHandler(KEYBOARD::IKeyboardHandler* handler); + /*! \brief Registers a handler to be called on mouse input (e.g a game client). + * + * \param handler The handler to call on mouse input. + * \return[in] The controller ID that serves as a context for incoming events. + * \sa IMouseButtonMap + */ + std::string RegisterMouseHandler(MOUSE::IMouseInputHandler* handler); + + /*! \brief Unregisters handler from mouse input. + * + * \param[in] handler The handler to unregister from mouse input. + */ + void UnregisterMouseHandler(MOUSE::IMouseInputHandler* handler); + private: /*! \brief Process keyboard event and translate into an action @@ -273,4 +317,17 @@ private: CCriticalSection m_actionMutex; std::vector<KEYBOARD::IKeyboardHandler*> m_keyboardHandlers; + + struct MouseHandlerHandle + { + MOUSE::IMouseInputHandler* inputHandler; + std::unique_ptr<MOUSE::IMouseDriverHandler> driverHandler; + }; + + std::vector<MouseHandlerHandle> m_mouseHandlers; + std::unique_ptr<MOUSE::IMouseButtonMap> m_mouseButtonMap; + + std::unique_ptr<KEYBOARD::IKeyboardHandler> m_keyboardEasterEgg; }; + +/// \} diff --git a/xbmc/input/joysticks/CMakeLists.txt b/xbmc/input/joysticks/CMakeLists.txt index ac5450df91..2e1ec43705 100644 --- a/xbmc/input/joysticks/CMakeLists.txt +++ b/xbmc/input/joysticks/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES DeadzoneFilter.cpp DefaultJoystick.cpp DriverPrimitive.cpp + JoystickEasterEgg.cpp JoystickMonitor.cpp JoystickTranslator.cpp KeymapHandler.cpp @@ -13,11 +14,13 @@ set(HEADERS DeadzoneFilter.h IButtonMap.h IButtonMapCallback.h IButtonMapper.h + IButtonSequence.h IDriverHandler.h IDriverReceiver.h IInputHandler.h IInputReceiver.h IKeymapHandler.h + JoystickEasterEgg.h JoystickMonitor.h JoystickTranslator.h JoystickTypes.h diff --git a/xbmc/input/joysticks/DefaultJoystick.cpp b/xbmc/input/joysticks/DefaultJoystick.cpp index 99c7c61f94..d474023850 100644 --- a/xbmc/input/joysticks/DefaultJoystick.cpp +++ b/xbmc/input/joysticks/DefaultJoystick.cpp @@ -20,6 +20,7 @@ #include "DefaultJoystick.h" #include "KeymapHandler.h" +#include "JoystickEasterEgg.h" #include "JoystickTranslator.h" #include "input/Key.h" #include "Application.h" @@ -34,7 +35,8 @@ using namespace JOYSTICK; CDefaultJoystick::CDefaultJoystick(void) : m_handler(new CKeymapHandler), - m_rumbleGenerator(ControllerID()) + m_rumbleGenerator(ControllerID()), + m_easterEgg(new CJoystickEasterEgg) { } @@ -75,6 +77,9 @@ INPUT_TYPE CDefaultJoystick::GetInputType(const FeatureName& feature) const bool CDefaultJoystick::OnButtonPress(const FeatureName& feature, bool bPressed) { + if (bPressed && m_easterEgg->OnButtonPress(feature)) + return true; + const unsigned int keyId = GetKeyID(feature); if (m_handler->GetInputType(keyId) == INPUT_TYPE::DIGITAL) diff --git a/xbmc/input/joysticks/DefaultJoystick.h b/xbmc/input/joysticks/DefaultJoystick.h index 548b9e116e..0c22e14a2c 100644 --- a/xbmc/input/joysticks/DefaultJoystick.h +++ b/xbmc/input/joysticks/DefaultJoystick.h @@ -25,6 +25,7 @@ #include "RumbleGenerator.h" #include <map> +#include <memory> #include <vector> #define DEFAULT_CONTROLLER_ID "game.controller.default" @@ -36,6 +37,7 @@ namespace JOYSTICK { class IKeymapHandler; + class IButtonSequence; /*! * \ingroup joystick @@ -99,5 +101,7 @@ namespace JOYSTICK // Rumble functionality CRumbleGenerator m_rumbleGenerator; + + std::unique_ptr<IButtonSequence> m_easterEgg; }; } diff --git a/xbmc/input/joysticks/IButtonSequence.h b/xbmc/input/joysticks/IButtonSequence.h new file mode 100644 index 0000000000..d2646582e4 --- /dev/null +++ b/xbmc/input/joysticks/IButtonSequence.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "JoystickTypes.h" + +namespace JOYSTICK +{ + class IButtonSequence + { + public: + virtual ~IButtonSequence() = default; + + virtual bool OnButtonPress(const FeatureName& feature) = 0; + }; +} diff --git a/xbmc/input/joysticks/JoystickEasterEgg.cpp b/xbmc/input/joysticks/JoystickEasterEgg.cpp new file mode 100644 index 0000000000..b163faf710 --- /dev/null +++ b/xbmc/input/joysticks/JoystickEasterEgg.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "JoystickEasterEgg.h" +#include "guilib/GUIAudioManager.h" +#include "guilib/WindowIDs.h" +#include "settings/Settings.h" + +using namespace JOYSTICK; + +std::vector<FeatureName> CJoystickEasterEgg::m_sequence = { + "up", + "up", + "down", + "down", + "left", + "right", + "left", + "right", + "b", + "a", +}; + +CJoystickEasterEgg::CJoystickEasterEgg(void) : + m_state(0) +{ +} + +bool CJoystickEasterEgg::OnButtonPress(const FeatureName& feature) +{ + bool bHandled = false; + + // Update state + if (feature == m_sequence[m_state]) + m_state++; + else + m_state = 0; + + // Capture input when finished with arrows (2 x up/down/left/right) + if (m_state > 8) + { + bHandled = true; + + if (m_state >= m_sequence.size()) + { + OnFinish(); + m_state = 0; + } + } + + return bHandled; +} + +void CJoystickEasterEgg::OnFinish(void) +{ + CSettings::GetInstance().ToggleBool(CSettings::SETTING_GAMES_ENABLE); + + WINDOW_SOUND sound = CSettings::GetInstance().GetBool(CSettings::SETTING_GAMES_ENABLE) ? SOUND_INIT : SOUND_DEINIT; + g_audioManager.PlayWindowSound(WINDOW_DIALOG_KAI_TOAST, sound); + + //! @todo Shake screen +} diff --git a/xbmc/input/joysticks/JoystickEasterEgg.h b/xbmc/input/joysticks/JoystickEasterEgg.h new file mode 100644 index 0000000000..fe239fcec7 --- /dev/null +++ b/xbmc/input/joysticks/JoystickEasterEgg.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "IButtonSequence.h" + +#include <vector> + +namespace JOYSTICK +{ + /*! + * \brief Hush!!! + */ + class CJoystickEasterEgg : public IButtonSequence + { + public: + CJoystickEasterEgg(void); + virtual ~CJoystickEasterEgg() = default; + + // implementation of IButtonSequence + virtual bool OnButtonPress(const FeatureName& feature) override; + + static void OnFinish(void); + + private: + static std::vector<FeatureName> m_sequence; + + unsigned int m_state; + }; +} diff --git a/xbmc/input/joysticks/JoystickTypes.h b/xbmc/input/joysticks/JoystickTypes.h index 6e774a94fe..f30ddcb1fa 100644 --- a/xbmc/input/joysticks/JoystickTypes.h +++ b/xbmc/input/joysticks/JoystickTypes.h @@ -42,6 +42,7 @@ namespace JOYSTICK * 2) analog stick * 3) accelerometer * 4) rumble motor + * 5) relative pointer * * [1] All three driver primitives (buttons, hats and axes) have a state that * can be represented using a single scalar value. For this reason, @@ -54,6 +55,7 @@ namespace JOYSTICK ANALOG_STICK, ACCELEROMETER, MOTOR, + RELPOINTER, }; /*! diff --git a/xbmc/input/joysticks/Makefile b/xbmc/input/joysticks/Makefile index cefbb9c1e9..c7e3a75c66 100644 --- a/xbmc/input/joysticks/Makefile +++ b/xbmc/input/joysticks/Makefile @@ -1,6 +1,7 @@ SRCS=DeadzoneFilter.cpp \ DefaultJoystick.cpp \ DriverPrimitive.cpp \ + JoystickEasterEgg.cpp \ JoystickMonitor.cpp \ JoystickTranslator.cpp \ KeymapHandler.cpp \ diff --git a/xbmc/input/keyboard/CMakeLists.txt b/xbmc/input/keyboard/CMakeLists.txt index 463c5d0f61..ed121ac30a 100644 --- a/xbmc/input/keyboard/CMakeLists.txt +++ b/xbmc/input/keyboard/CMakeLists.txt @@ -1 +1,6 @@ -set(HEADERS IKeyboardHandler.h) +set(SOURCES KeyboardEasterEgg.cpp) + +set(HEADERS IKeyboardHandler.h + KeyboardEasterEgg.h) + +core_add_library(input_keyboard) diff --git a/xbmc/input/keyboard/KeyboardEasterEgg.cpp b/xbmc/input/keyboard/KeyboardEasterEgg.cpp new file mode 100644 index 0000000000..a2f089256c --- /dev/null +++ b/xbmc/input/keyboard/KeyboardEasterEgg.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "input/keyboard/KeyboardEasterEgg.h" +#include "input/joysticks/JoystickEasterEgg.h" +#include "input/Key.h" + +using namespace KEYBOARD; + +std::vector<XBMCVKey> CKeyboardEasterEgg::m_sequence = { + XBMCVK_UP, + XBMCVK_UP, + XBMCVK_DOWN, + XBMCVK_DOWN, + XBMCVK_LEFT, + XBMCVK_RIGHT, + XBMCVK_LEFT, + XBMCVK_RIGHT, + XBMCVK_B, + XBMCVK_A, +}; + +CKeyboardEasterEgg::CKeyboardEasterEgg(void) : + m_state(0) +{ +} + +bool CKeyboardEasterEgg::OnKeyPress(const CKey& key) +{ + bool bHandled = false; + + // Update state + if (key.GetVKey() == m_sequence[m_state]) + m_state++; + else + m_state = 0; + + // Capture input when finished with arrows (2 x up/down/left/right) + if (m_state > 8) + { + bHandled = true; + + if (m_state >= m_sequence.size()) + { + JOYSTICK::CJoystickEasterEgg::OnFinish(); + m_state = 0; + } + } + + return bHandled; +} diff --git a/xbmc/input/keyboard/KeyboardEasterEgg.h b/xbmc/input/keyboard/KeyboardEasterEgg.h new file mode 100644 index 0000000000..7d3888644c --- /dev/null +++ b/xbmc/input/keyboard/KeyboardEasterEgg.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "IKeyboardHandler.h" +#include "input/XBMC_vkeys.h" + +#include <vector> + +namespace KEYBOARD +{ + /*! + * \brief Hush!!! + */ + class CKeyboardEasterEgg : public IKeyboardHandler + { + public: + CKeyboardEasterEgg(void); + virtual ~CKeyboardEasterEgg() = default; + + // implementation of IKeyboardHandler + virtual bool OnKeyPress(const CKey& key); + virtual void OnKeyRelease(const CKey& key) { } + + private: + static std::vector<XBMCVKey> m_sequence; + + unsigned int m_state; + }; +} diff --git a/xbmc/input/keyboard/Makefile b/xbmc/input/keyboard/Makefile new file mode 100644 index 0000000000..939a34ed96 --- /dev/null +++ b/xbmc/input/keyboard/Makefile @@ -0,0 +1,6 @@ +SRCS=KeyboardEasterEgg.cpp \ + +LIB=input_keyboard.a + +include ../../../Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) diff --git a/xbmc/input/mouse/CMakeLists.txt b/xbmc/input/mouse/CMakeLists.txt new file mode 100644 index 0000000000..5a2f7b4560 --- /dev/null +++ b/xbmc/input/mouse/CMakeLists.txt @@ -0,0 +1,8 @@ +set(SOURCES MouseWindowingButtonMap.cpp) + +set(HEADERS IMouseButtonMap.h + IMouseDriverHandler.h + IMouseInputHandler.h + MouseWindowingButtonMap.h) + +core_add_library(input_mouse) diff --git a/xbmc/input/mouse/IMouseButtonMap.h b/xbmc/input/mouse/IMouseButtonMap.h new file mode 100644 index 0000000000..80d812da4f --- /dev/null +++ b/xbmc/input/mouse/IMouseButtonMap.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include <string> + +namespace MOUSE +{ + /*! + * \ingroup mouse + * \brief Button map interface to translate between the mouse's driver data + * and its higher-level features. + */ + class IMouseButtonMap + { + public: + virtual ~IMouseButtonMap(void) = default; + + /*! + * \brief The ID of the controller profile associated with this button map + * + * The controller ID provided by the implementation serves as the context + * for the feature names below. + * + * \return The ID of this button map's controller profile + */ + virtual std::string ControllerID(void) const = 0; + + /*! + * \brief Get the name of a button by its index + * + * \param buttonIndex The index of the button + * \param[out] feature The name of the feature with the specified button index + * + * \return True if the button index is associated with a feature, false otherwise + */ + virtual bool GetButton(unsigned int buttonIndex, std::string& feature) = 0; + + /*! + * \brief Get the name of the mouse's relative pointer + * + * \param[out] feature The name of the relative pointer + * + * \return True if the mouse has a relative pointer, false otherwise + */ + virtual bool GetRelativePointer(std::string& feature) = 0; + + /*! + * \brief Get the button index for a button + * + * \param feature The name of the button + * \param buttonIndex The resolved button index + * + * \return True if the feature resolved to a button index, or false otherwise + */ + virtual bool GetButtonIndex(const std::string& feature, unsigned int& buttonIndex) = 0; + }; +} diff --git a/xbmc/input/mouse/IMouseDriverHandler.h b/xbmc/input/mouse/IMouseDriverHandler.h new file mode 100644 index 0000000000..9da004a4a4 --- /dev/null +++ b/xbmc/input/mouse/IMouseDriverHandler.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +namespace MOUSE +{ + /*! + * \ingroup mouse + * \brief Interface for handling mouse driver events + */ + class IMouseDriverHandler + { + public: + virtual ~IMouseDriverHandler(void) = default; + + /*! + * \brief Handle mouse position updates + * + * \param x The new x coordinate of the pointer + * \param y The new y coordinate of the pointer + * + * \return True if the event was handled, false otherwise + */ + virtual bool OnPosition(int x, int y) = 0; + + /*! + * \brief A mouse button has been pressed + * + * \param button The index of the pressed button + * + * \return True if the event was handled, otherwise false + */ + virtual bool OnButtonPress(unsigned int button) = 0; + + /*! + * \brief A mouse button has been released + * + * \param button The index of the released button + */ + virtual void OnButtonRelease(unsigned int button) = 0; + }; +} diff --git a/xbmc/input/mouse/IMouseInputHandler.h b/xbmc/input/mouse/IMouseInputHandler.h new file mode 100644 index 0000000000..6eda79b954 --- /dev/null +++ b/xbmc/input/mouse/IMouseInputHandler.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include <string> + +namespace MOUSE +{ + /*! + * \ingroup mouse + * \brief Interface for handling mouse events + */ + class IMouseInputHandler + { + public: + virtual ~IMouseInputHandler(void) = default; + + /*! + * \brief The controller profile for this mouse input handler + * + * \return The ID of the add-on extending kodi.game.controller + */ + virtual std::string ControllerID(void) const = 0; + + /*! + * \brief A relative pointer has moved + * + * \param relpointer The name of the relative pointer being moved + * \param dx The relative x coordinate of motion + * \param dy The relative y coordinate of motion + * + * \return True if the event was handled, otherwise false + */ + virtual bool OnMotion(const std::string& relpointer, int dx, int dy) = 0; + + /*! + * \brief A mouse button has been pressed + * + * \param button The name of the feature being pressed + * + * \return True if the event was handled, otherwise false + */ + virtual bool OnButtonPress(const std::string& button) = 0; + + /*! + * \brief A mouse button has been released + * + * \param button The name of the feature being released + */ + virtual void OnButtonRelease(const std::string& button) = 0; + }; +} diff --git a/xbmc/input/mouse/Makefile b/xbmc/input/mouse/Makefile new file mode 100644 index 0000000000..62f682d704 --- /dev/null +++ b/xbmc/input/mouse/Makefile @@ -0,0 +1,6 @@ +SRCS=MouseWindowingButtonMap.cpp \ + +LIB=input_mouse.a + +include ../../../Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) diff --git a/xbmc/input/mouse/MouseWindowingButtonMap.cpp b/xbmc/input/mouse/MouseWindowingButtonMap.cpp new file mode 100644 index 0000000000..e2da4ae005 --- /dev/null +++ b/xbmc/input/mouse/MouseWindowingButtonMap.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "MouseWindowingButtonMap.h" +#include "input/MouseStat.h" + +#include <algorithm> + +using namespace MOUSE; + +#define CONTROLLER_PROFILE "game.controller.mouse" + +std::vector<std::pair<unsigned int, std::string>> CMouseWindowingButtonMap::m_buttonMap = { + { XBMC_BUTTON_LEFT, "left" }, + { XBMC_BUTTON_MIDDLE, "middle" }, + { XBMC_BUTTON_RIGHT, "right" }, + { XBMC_BUTTON_WHEELUP, "wheelup" }, + { XBMC_BUTTON_WHEELDOWN, "wheeldown" }, +}; + +std::string CMouseWindowingButtonMap::m_pointerName = "pointer"; + +std::string CMouseWindowingButtonMap::ControllerID(void) const +{ + return CONTROLLER_PROFILE; +} + +bool CMouseWindowingButtonMap::GetButton(unsigned int buttonIndex, std::string& feature) +{ + auto it = std::find_if(m_buttonMap.begin(), m_buttonMap.end(), + [buttonIndex](const std::pair<unsigned int, std::string>& entry) + { + return entry.first == buttonIndex; + }); + + if (it != m_buttonMap.end()) + { + feature = it->second; + return true; + } + + return false; +} + +bool CMouseWindowingButtonMap::GetRelativePointer(std::string& feature) +{ + feature = m_pointerName; + return true; +} + +bool CMouseWindowingButtonMap::GetButtonIndex(const std::string& feature, unsigned int& buttonIndex) +{ + auto it = std::find_if(m_buttonMap.begin(), m_buttonMap.end(), + [&feature](const std::pair<unsigned int, std::string>& entry) + { + return entry.second == feature; + }); + + if (it != m_buttonMap.end()) + { + buttonIndex = it->first; + return true; + } + + return false; +} diff --git a/xbmc/input/mouse/MouseWindowingButtonMap.h b/xbmc/input/mouse/MouseWindowingButtonMap.h new file mode 100644 index 0000000000..a728257178 --- /dev/null +++ b/xbmc/input/mouse/MouseWindowingButtonMap.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "input/mouse/IMouseButtonMap.h" + +#include <string> +#include <utility> +#include <vector> + +namespace MOUSE +{ + /*! + * \ingroup mouse + * \brief Maps mouse windowing events to higher-level features understood by IMouseInputHandler implementations. + */ + class CMouseWindowingButtonMap : public IMouseButtonMap + { + public: + virtual ~CMouseWindowingButtonMap(void) = default; + + // implementation of IMouseButtonMap + virtual std::string ControllerID(void) const override; + virtual bool GetButton(unsigned int buttonIndex, std::string& feature) override; + virtual bool GetRelativePointer(std::string& feature) override; + virtual bool GetButtonIndex(const std::string& feature, unsigned int& buttonIndex) override; + + private: + static std::vector<std::pair<unsigned int, std::string>> m_buttonMap; + static std::string m_pointerName; + }; +} diff --git a/xbmc/input/mouse/generic/CMakeLists.txt b/xbmc/input/mouse/generic/CMakeLists.txt new file mode 100644 index 0000000000..b5c28585b6 --- /dev/null +++ b/xbmc/input/mouse/generic/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SOURCES MouseInputHandling.cpp) + +set(HEADERS MouseInputHandling.h) + +core_add_library(input_mouse_generic) diff --git a/xbmc/input/mouse/generic/Makefile b/xbmc/input/mouse/generic/Makefile new file mode 100644 index 0000000000..0170855539 --- /dev/null +++ b/xbmc/input/mouse/generic/Makefile @@ -0,0 +1,6 @@ +SRCS=MouseInputHandling.cpp \ + +LIB=input_mouse_generic.a + +include ../../../../Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) diff --git a/xbmc/input/mouse/generic/MouseInputHandling.cpp b/xbmc/input/mouse/generic/MouseInputHandling.cpp new file mode 100644 index 0000000000..641dd48ecb --- /dev/null +++ b/xbmc/input/mouse/generic/MouseInputHandling.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "MouseInputHandling.h" +#include "input/mouse/IMouseButtonMap.h" +#include "input/mouse/IMouseInputHandler.h" + +using namespace MOUSE; + +CMouseInputHandling::CMouseInputHandling(IMouseInputHandler* handler, IMouseButtonMap* buttonMap) : + m_handler(handler), + m_buttonMap(buttonMap), + m_x(0), + m_y(0) +{ +} + +bool CMouseInputHandling::OnPosition(int x, int y) +{ + int dx = x - m_x; + int dy = y - m_y; + + bool bHandled = false; + + std::string featureName; + if (m_buttonMap->GetRelativePointer(featureName)) + bHandled = m_handler->OnMotion(featureName, dx, dy); + + m_x = x; + m_y = y; + + return bHandled; +} + +bool CMouseInputHandling::OnButtonPress(unsigned int button) +{ + bool bHandled = false; + + std::string featureName; + if (m_buttonMap->GetButton(button, featureName)) + bHandled = m_handler->OnButtonPress(featureName); + + return bHandled; +} + +void CMouseInputHandling::OnButtonRelease(unsigned int button) +{ + std::string featureName; + if (m_buttonMap->GetButton(button, featureName)) + m_handler->OnButtonRelease(featureName); +} diff --git a/xbmc/input/mouse/generic/MouseInputHandling.h b/xbmc/input/mouse/generic/MouseInputHandling.h new file mode 100644 index 0000000000..be1f7a94b6 --- /dev/null +++ b/xbmc/input/mouse/generic/MouseInputHandling.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "input/mouse/IMouseDriverHandler.h" + +namespace MOUSE +{ + class IMouseInputHandler; + class IMouseButtonMap; + class CRelativePointer; + + /*! + * \ingroup mouse + * \brief Class to translate input from driver info to higher-level features + */ + class CMouseInputHandling : public IMouseDriverHandler + { + public: + CMouseInputHandling(IMouseInputHandler* handler, IMouseButtonMap* buttonMap); + + virtual ~CMouseInputHandling(void) = default; + + // implementation of IMouseDriverHandler + virtual bool OnPosition(int x, int y) override; + virtual bool OnButtonPress(unsigned int button) override; + virtual void OnButtonRelease(unsigned int button) override; + + private: + // Construction parameters + IMouseInputHandler* const m_handler; + IMouseButtonMap* const m_buttonMap; + + // Mouse parameters + int m_x; + int m_y; + }; +} diff --git a/xbmc/interfaces/builtins/AddonBuiltins.cpp b/xbmc/interfaces/builtins/AddonBuiltins.cpp index 5b5eb7ef8c..b4506abf38 100644 --- a/xbmc/interfaces/builtins/AddonBuiltins.cpp +++ b/xbmc/interfaces/builtins/AddonBuiltins.cpp @@ -31,12 +31,15 @@ #include "addons/RepositoryUpdater.h" #include "FileItem.h" #include "filesystem/PluginDirectory.h" +#include "games/tags/GameInfoTag.h" #include "guilib/GUIWindowManager.h" #include "GUIUserMessages.h" #include "interfaces/generic/ScriptInvocationManager.h" #include "utils/log.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" +#include "Application.h" +#include "PlayListPlayer.h" #if defined(TARGET_DARWIN) #include "filesystem/SpecialProtocol.h" @@ -80,7 +83,7 @@ static int RunPlugin(const std::vector<std::string>& params) return 0; } -/*! \brief Run a script or plugin add-on. +/*! \brief Run a script, plugin or game add-on. * \param params The parameters. * \details params[0] = add-on id. * params[1] is blank for no add-on parameters @@ -140,6 +143,24 @@ static int RunAddon(const std::vector<std::string>& params) // (params[1] ... params[x]) separated by a comma to RunScript CBuiltins::GetInstance().Execute(StringUtils::Format("RunScript(%s)", StringUtils::Join(params, ",").c_str())); } + else if (CAddonMgr::GetInstance().GetAddon(addonid, addon, ADDON_GAMEDLL)) + { + CFileItem item; + + if (params.size() >= 2) + { + item = CFileItem(params[1], false); + item.GetGameInfoTag()->SetGameClient(addonid); + } + else + item = CFileItem(addon); + + if (!g_application.PlayMedia(item, "", PLAYLIST_NONE)) + { + CLog::Log(LOGERROR, "RunAddon could not start %s", addonid.c_str()); + return false; + } + } else CLog::Log(LOGERROR, "RunAddon: unknown add-on id '%s', or unexpected add-on type (not a script or plugin).", addonid.c_str()); } diff --git a/xbmc/interfaces/json-rpc/AddonsOperations.cpp b/xbmc/interfaces/json-rpc/AddonsOperations.cpp index 272620b20c..e0de3e0380 100644 --- a/xbmc/interfaces/json-rpc/AddonsOperations.cpp +++ b/xbmc/interfaces/json-rpc/AddonsOperations.cpp @@ -62,6 +62,9 @@ JSONRPC_STATUS CAddonsOperations::GetAddons(const std::string &method, ITranspor case ADDON_IMAGE: content = CPluginSource::IMAGE; break; + case ADDON_GAME: + content = CPluginSource::GAME; + break; case ADDON_EXECUTABLE: content = CPluginSource::EXECUTABLE; break; diff --git a/xbmc/peripherals/Peripherals.cpp b/xbmc/peripherals/Peripherals.cpp index d5ccff16be..0abf0ef0d4 100644 --- a/xbmc/peripherals/Peripherals.cpp +++ b/xbmc/peripherals/Peripherals.cpp @@ -77,12 +77,14 @@ using namespace XFILE; CPeripherals::CPeripherals() : m_eventScanner(this) { + RegisterObserver(&m_portMapper); Clear(); } CPeripherals::~CPeripherals() { Clear(); + UnregisterObserver(&m_portMapper); } CPeripherals &CPeripherals::GetInstance() diff --git a/xbmc/peripherals/Peripherals.h b/xbmc/peripherals/Peripherals.h index e7428df2e0..6495b5ecba 100644 --- a/xbmc/peripherals/Peripherals.h +++ b/xbmc/peripherals/Peripherals.h @@ -24,6 +24,7 @@ #include "EventScanner.h" #include "bus/PeripheralBus.h" #include "devices/Peripheral.h" +#include "games/ports/PortMapper.h" //! @todo Find me a better place #include "messaging/IMessageTarget.h" #include "settings/lib/ISettingCallback.h" #include "system.h" @@ -306,6 +307,7 @@ namespace PERIPHERALS std::vector<PeripheralBusPtr> m_busses; std::vector<PeripheralDeviceMapping> m_mappings; CEventScanner m_eventScanner; + GAME::CPortMapper m_portMapper; //! @todo Find me a better place CCriticalSection m_critSection; CCriticalSection m_critSectionBusses; CCriticalSection m_critSectionMappings; diff --git a/xbmc/profiles/ProfilesManager.cpp b/xbmc/profiles/ProfilesManager.cpp index 54f6b8c438..c31579b973 100644 --- a/xbmc/profiles/ProfilesManager.cpp +++ b/xbmc/profiles/ProfilesManager.cpp @@ -354,6 +354,7 @@ void CProfilesManager::CreateProfileFolders() CDirectory::Create(GetThumbnailsFolder()); CDirectory::Create(GetVideoThumbFolder()); CDirectory::Create(GetBookmarksThumbFolder()); + CDirectory::Create(GetSavestatesFolder()); for (size_t hex = 0; hex < 16; hex++) CDirectory::Create(URIUtils::AddFileToFolder(GetThumbnailsFolder(), StringUtils::Format("%lx", hex))); @@ -505,6 +506,14 @@ std::string CProfilesManager::GetLibraryFolder() const return URIUtils::AddFileToFolder(GetUserDataFolder(), "library"); } +std::string CProfilesManager::GetSavestatesFolder() const +{ + if (GetCurrentProfile().hasDatabases()) + return URIUtils::AddFileToFolder(GetProfileUserDataFolder(), "Savestates"); + + return URIUtils::AddFileToFolder(GetUserDataFolder(), "Savestates"); +} + std::string CProfilesManager::GetSettingsFile() const { std::string settings; diff --git a/xbmc/profiles/ProfilesManager.h b/xbmc/profiles/ProfilesManager.h index f0bcab87ed..3b50860b3c 100644 --- a/xbmc/profiles/ProfilesManager.h +++ b/xbmc/profiles/ProfilesManager.h @@ -171,6 +171,7 @@ public: std::string GetVideoThumbFolder() const; std::string GetBookmarksThumbFolder() const; std::string GetLibraryFolder() const; + std::string GetSavestatesFolder() const; std::string GetSettingsFile() const; // uses HasSlashAtEnd to determine if a directory or file was meant diff --git a/xbmc/settings/AdvancedSettings.cpp b/xbmc/settings/AdvancedSettings.cpp index cc37998f0c..6f9723e926 100644 --- a/xbmc/settings/AdvancedSettings.cpp +++ b/xbmc/settings/AdvancedSettings.cpp @@ -1159,6 +1159,23 @@ void CAdvancedSettings::ParseSettingsFile(const std::string &file) XMLUtils::GetBoolean(pDatabase, "compression", m_databaseEpg.compression); } + pDatabase = pRootElement->FirstChildElement("savestatedatabase"); + if (pDatabase) + { + XMLUtils::GetString(pDatabase, "type", m_databaseSavestates.type); + XMLUtils::GetString(pDatabase, "host", m_databaseSavestates.host); + XMLUtils::GetString(pDatabase, "port", m_databaseSavestates.port); + XMLUtils::GetString(pDatabase, "user", m_databaseSavestates.user); + XMLUtils::GetString(pDatabase, "pass", m_databaseSavestates.pass); + XMLUtils::GetString(pDatabase, "name", m_databaseSavestates.name); + XMLUtils::GetString(pDatabase, "key", m_databaseSavestates.key); + XMLUtils::GetString(pDatabase, "cert", m_databaseSavestates.cert); + XMLUtils::GetString(pDatabase, "ca", m_databaseSavestates.ca); + XMLUtils::GetString(pDatabase, "capath", m_databaseSavestates.capath); + XMLUtils::GetString(pDatabase, "ciphers", m_databaseSavestates.ciphers); + XMLUtils::GetBoolean(pDatabase, "compression", m_databaseSavestates.compression); + } + pElement = pRootElement->FirstChildElement("enablemultimediakeys"); if (pElement) { diff --git a/xbmc/settings/AdvancedSettings.h b/xbmc/settings/AdvancedSettings.h index fc526d11c3..1b747edc0c 100644 --- a/xbmc/settings/AdvancedSettings.h +++ b/xbmc/settings/AdvancedSettings.h @@ -336,6 +336,7 @@ class CAdvancedSettings : public ISettingCallback, public ISettingsHandler DatabaseSettings m_databaseTV; // advanced tv database setup DatabaseSettings m_databaseEpg; /*!< advanced EPG database setup */ DatabaseSettings m_databaseADSP; /*!< advanced audio dsp database setup */ + DatabaseSettings m_databaseSavestates; /*!< advanced savestate database setup */ bool m_guiVisualizeDirtyRegions; int m_guiAlgorithmDirtyRegions; diff --git a/xbmc/settings/Settings.cpp b/xbmc/settings/Settings.cpp index f7c86e6f81..13c5aaaa3e 100644 --- a/xbmc/settings/Settings.cpp +++ b/xbmc/settings/Settings.cpp @@ -431,6 +431,9 @@ const std::string CSettings::SETTING_GAMES_KEYBOARD_PLAYERCONFIG_5 = "gameskeybo const std::string CSettings::SETTING_GAMES_KEYBOARD_PLAYERCONFIG_6 = "gameskeyboard.keyboardplayerconfig6"; const std::string CSettings::SETTING_GAMES_KEYBOARD_PLAYERCONFIG_7 = "gameskeyboard.keyboardplayerconfig7"; const std::string CSettings::SETTING_GAMES_KEYBOARD_PLAYERCONFIG_8 = "gameskeyboard.keyboardplayerconfig8"; +const std::string CSettings::SETTING_GAMES_ENABLE = "gamesgeneral.enable"; +const std::string CSettings::SETTING_GAMES_ENABLEREWIND = "gamesgeneral.enablerewind"; +const std::string CSettings::SETTING_GAMES_REWINDTIME = "gamesgeneral.rewindtime"; CSettings::CSettings() : m_initialized(false) @@ -1227,6 +1230,8 @@ void CSettings::InitializeISettingCallbacks() settingSet.insert(CSettings::SETTING_GAMES_KEYBOARD_PLAYERCONFIG_6); settingSet.insert(CSettings::SETTING_GAMES_KEYBOARD_PLAYERCONFIG_7); settingSet.insert(CSettings::SETTING_GAMES_KEYBOARD_PLAYERCONFIG_8); + settingSet.insert(CSettings::SETTING_GAMES_ENABLEREWIND); + settingSet.insert(CSettings::SETTING_GAMES_REWINDTIME); m_settingsManager->RegisterCallback(&GAME::CGameSettings::GetInstance(), settingSet); } diff --git a/xbmc/settings/Settings.h b/xbmc/settings/Settings.h index 40e164cbf3..29b37283f8 100644 --- a/xbmc/settings/Settings.h +++ b/xbmc/settings/Settings.h @@ -387,6 +387,9 @@ public: static const std::string SETTING_GAMES_KEYBOARD_PLAYERCONFIG_6; static const std::string SETTING_GAMES_KEYBOARD_PLAYERCONFIG_7; static const std::string SETTING_GAMES_KEYBOARD_PLAYERCONFIG_8; + static const std::string SETTING_GAMES_ENABLE; + static const std::string SETTING_GAMES_ENABLEREWIND; + static const std::string SETTING_GAMES_REWINDTIME; /*! \brief Creates a new settings wrapper around a new settings manager. diff --git a/xbmc/utils/Observer.h b/xbmc/utils/Observer.h index 3b597559e6..49eec5b8fc 100644 --- a/xbmc/utils/Observer.h +++ b/xbmc/utils/Observer.h @@ -43,7 +43,9 @@ typedef enum ObservableMessageRecordings, ObservableMessagePeripheralsChanged, ObservableMessageChannelGroupsLoaded, - ObservableMessageManagerStopped + ObservableMessageManagerStopped, + ObservableMessagePortsChanged, + ObservableMessageSettingsChanged, } ObservableMessage; class Observer |