patch version: 2.25.05-11884-802 (76c92a2c)
patch date: 2025-05-06 01:20:39 UTC (+00:00)
---
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4b08cfc0..98dccbbf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -534,6 +534,22 @@ endif (CONFIG_STREAMRELAY MATCHES "Y" AND NOT MODULE_STREAMRELAY EQUAL 1)
 # Manage config.h based on command line parameters
 # Manipulate config file based on given parameters and read unset parameters
 
+execute_process (COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/config.sh --enabled WITH_EMU OUTPUT_VARIABLE CONFIG_WITH_EMU OUTPUT_STRIP_TRAILING_WHITESPACE)
+if (CONFIG_WITH_EMU MATCHES "Y" AND NOT WITH_EMU EQUAL 1)
+	add_definitions ("-DWITH_EMU")
+	set (WITH_EMU "1")
+	message (STATUS "  EMU is added by config compiling with EMU")
+endif (CONFIG_WITH_EMU MATCHES "Y" AND NOT WITH_EMU EQUAL 1)
+
+if (WITH_EMU)
+	execute_process (COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/config.sh --enabled WITH_SOFTCAM OUTPUT_VARIABLE CONFIG_WITH_SOFTCAM OUTPUT_STRIP_TRAILING_WHITESPACE)
+	if (CONFIG_WITH_SOFTCAM MATCHES "Y" AND NOT WITH_SOFTCAM EQUAL 1)
+		add_definitions ("-DWITH_SOFTCAM")
+		set (WITH_SOFTCAM "1")
+		message (STATUS "  SOFTCAM is added by config linking SoftCam.Key")
+	endif (CONFIG_WITH_SOFTCAM MATCHES "Y" AND NOT WITH_SOFTCAM EQUAL 1)
+endif (WITH_EMU)
+
 execute_process (COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/config.sh --show-valid OUTPUT_VARIABLE config_vars_string OUTPUT_STRIP_TRAILING_WHITESPACE)
 string(REGEX MATCHALL "[A-Z0-9_]+" config_vars ${config_vars_string})
 
@@ -879,6 +895,24 @@ endif( HAVE_LIBRT OR HAVE_LIBRT_STATIC)
 
 #--------------------------------------------------------------------------------
 
+if (NOT OSCamOperatingSystem MATCHES "Mac OS X")
+if (NOT DEFINED ENV{ANDROID_NDK})
+if (NOT DEFINED ENV{ANDROID_STANDALONE_TOOLCHAIN})
+	if (WITH_SOFTCAM)
+		if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/SoftCam.Key)
+				execute_process (COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/SoftCam.Key ${CMAKE_CURRENT_BINARY_DIR}/SoftCam.Key)
+		else (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/SoftCam.Key)
+				execute_process (COMMAND touch ${CMAKE_CURRENT_BINARY_DIR}/SoftCam.Key)
+		endif (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/SoftCam.Key)
+		set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--format=binary -Wl,SoftCam.Key -Wl,--format=default")
+		if (NOT OSCamOperatingSystem MATCHES "Windows/Cygwin")
+			set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,noexecstack")
+		endif (NOT OSCamOperatingSystem MATCHES "Windows/Cygwin")
+	endif (WITH_SOFTCAM)
+endif (NOT DEFINED ENV{ANDROID_STANDALONE_TOOLCHAIN})
+endif (NOT DEFINED ENV{ANDROID_NDK})
+endif (NOT OSCamOperatingSystem MATCHES "Mac OS X")
+
 #----------------------- installation -----------------------------
 
 file (GLOB config_files "${CMAKE_CURRENT_SOURCE_DIR}/Distribution/oscam.*")
@@ -991,4 +1025,11 @@ if (HAVE_LIBDVBCSA)
 	endif(STATICLIBDVBCSA AND NOT LIBDVBCSADIR)
 endif (HAVE_LIBDVBCSA)
 
+if (WITH_EMU)
+	message (STATUS "  Compile with EMU support")
+	if (WITH_SOFTCAM)
+		message (STATUS "  SoftCam.Key will be linked as well")
+	endif (WITH_SOFTCAM)
+endif (WITH_EMU)
+
 message (STATUS "")
diff --git a/Distribution/doc/html/oscam.server.5.html b/Distribution/doc/html/oscam.server.5.html
index 3fb60c56..48fc01d2 100644
--- a/Distribution/doc/html/oscam.server.5.html
+++ b/Distribution/doc/html/oscam.server.5.html
@@ -840,6 +840,13 @@ set reader's CCcam reshare hop, default:0
 
 <P>
 
+<B>cccwantemu</B> = <B>0</B>|<B>1</B>
+<DL COMPACT><DT><DD>
+1 = request to provide emu from CCCam server, too, default:0
+</DL>
+
+<P>
+
 <B>ccckeepalive</B> = <B>0</B>|<B>1</B>
 <DL COMPACT><DT><DD>
 1 = send keepalive messages to keep connection to remote CCCam server up, default:0
diff --git a/Distribution/doc/man/oscam.server.5 b/Distribution/doc/man/oscam.server.5
index 4b5db81e..3a94758d 100644
--- a/Distribution/doc/man/oscam.server.5
+++ b/Distribution/doc/man/oscam.server.5
@@ -649,6 +649,11 @@ set reader's CCcam reshare hop, default:0
   \fBx\fP = resharing for direct peer and share level x
 .RE
 .PP
+\fBcccwantemu\fP = \fB0\fP|\fB1\fP
+.RS 3n
+1 = request to provide emu from CCCam server, too, default:0
+.RE
+.PP
 \fBccckeepalive\fP = \fB0\fP|\fB1\fP
 .RS 3n
 1 = send keepalive messages to keep connection to remote CCCam server up, default:0
diff --git a/Distribution/doc/txt/oscam.server.txt b/Distribution/doc/txt/oscam.server.txt
index 3d333a93..08b173ed 100644
--- a/Distribution/doc/txt/oscam.server.txt
+++ b/Distribution/doc/txt/oscam.server.txt
@@ -478,6 +478,9 @@ DESCRIPTIONS
 	    0 = resharing for direct peer only
 	    x = resharing for direct peer and share level x
 
+       cccwantemu = 0|1
+	  1 = request to provide emu from CCCam server, too, default:0
+
        ccckeepalive = 0|1
 	  1 = send keepalive messages to keep connection to remote CCCam server up, default:0
 
diff --git a/Makefile b/Makefile
index ea8946c1..a95e2c04 100644
--- a/Makefile
+++ b/Makefile
@@ -32,6 +32,9 @@ ifeq "$(shell ./config.sh --enabled WITH_SSL)" "Y"
 	override USE_SSL=1
 	override USE_LIBCRYPTO=1
 endif
+ifeq "$(shell ./config.sh --enabled WITH_EMU)" "Y"
+	override USE_LIBCRYPTO=1
+endif
 ifdef USE_SSL
 	override USE_LIBCRYPTO=1
 endif
@@ -377,6 +380,32 @@ SRC-$(CONFIG_CS_CACHEEX) += module-cccam-cacheex.c
 SRC-$(CONFIG_MODULE_CCCAM) += module-cccam.c
 SRC-$(CONFIG_MODULE_CCCSHARE) += module-cccshare.c
 SRC-$(CONFIG_MODULE_CONSTCW) += module-constcw.c
+SRC-$(CONFIG_WITH_EMU) += module-emulator.c
+SRC-$(CONFIG_WITH_EMU) += module-emulator-osemu.c
+SRC-$(CONFIG_WITH_EMU) += module-emulator-biss.c
+SRC-$(CONFIG_WITH_EMU) += module-emulator-cryptoworks.c
+SRC-$(CONFIG_WITH_EMU) += module-emulator-director.c
+SRC-$(CONFIG_WITH_EMU) += module-emulator-irdeto.c
+SRC-$(CONFIG_WITH_EMU) += module-emulator-nagravision.c
+SRC-$(CONFIG_WITH_EMU) += module-emulator-omnicrypt.c
+SRC-$(CONFIG_WITH_EMU) += module-emulator-powervu.c
+SRC-$(CONFIG_WITH_EMU) += module-emulator-viaccess.c
+ifeq "$(CONFIG_WITH_EMU)" "y"
+ifeq "$(CONFIG_WITH_SOFTCAM)" "y"
+UNAME := $(shell uname -s)
+ifneq ($(UNAME),Darwin)
+ifndef ANDROID_NDK
+ifndef ANDROID_STANDALONE_TOOLCHAIN
+TOUCH_SK := $(shell touch SoftCam.Key)
+override LDFLAGS += -Wl,--format=binary -Wl,SoftCam.Key -Wl,--format=default
+ifneq ($(uname_S),Cygwin)
+override LDFLAGS += -Wl,-z,noexecstack
+endif
+endif
+endif
+endif
+endif
+endif
 SRC-$(CONFIG_CS_CACHEEX) += module-csp.c
 SRC-$(CONFIG_CW_CYCLE_CHECK) += module-cw-cycle-check.c
 SRC-$(CONFIG_WITH_AZBOX) += module-dvbapi-azbox.c
diff --git a/README.config b/README.config
index 03c70f7d..9b99c3c4 100644
--- a/README.config
+++ b/README.config
@@ -81,7 +81,7 @@ Examples:
   ./config.sh --create-cert ecdsa prime256v1 ca 'My OSCam Distribution'
 
 Available options:
-       addons: WEBIF WEBIF_LIVELOG WEBIF_JQUERY WITH_COMPRESS_WEBIF WITH_SSL HAVE_DVBAPI WITH_EXTENDED_CW WITH_NEUTRINO READ_SDT_CHARSETS CS_ANTICASC WITH_DEBUG MODULE_MONITOR WITH_LB CS_CACHEEX CS_CACHEEX_AIO CW_CYCLE_CHECK LCDSUPPORT LEDSUPPORT CLOCKFIX IPV6SUPPORT WITH_ARM_NEON WITH_SIGNING
+       addons: WEBIF WEBIF_LIVELOG WEBIF_JQUERY WITH_COMPRESS_WEBIF WITH_SSL HAVE_DVBAPI WITH_EXTENDED_CW WITH_NEUTRINO READ_SDT_CHARSETS CS_ANTICASC WITH_DEBUG MODULE_MONITOR WITH_LB CS_CACHEEX CS_CACHEEX_AIO CW_CYCLE_CHECK LCDSUPPORT LEDSUPPORT CLOCKFIX IPV6SUPPORT WITH_ARM_NEON WITH_SIGNING WITH_EMU WITH_SOFTCAM
     protocols: MODULE_CAMD33 MODULE_CAMD35 MODULE_CAMD35_TCP MODULE_NEWCAMD MODULE_CCCAM MODULE_CCCSHARE MODULE_GBOX MODULE_RADEGAST MODULE_SCAM MODULE_SERIAL MODULE_CONSTCW MODULE_PANDORA MODULE_GHTTP MODULE_STREAMRELAY
       readers: READER_NAGRA READER_NAGRA_MERLIN READER_IRDETO READER_CONAX READER_CRYPTOWORKS READER_SECA READER_VIACCESS READER_VIDEOGUARD READER_DRE READER_TONGFANG READER_BULCRYPT READER_GRIFFIN READER_DGCRYPT
  card_readers: CARDREADER_PHOENIX CARDREADER_INTERNAL CARDREADER_SC8IN1 CARDREADER_MP35 CARDREADER_SMARGO CARDREADER_DB2COM CARDREADER_STAPI CARDREADER_STAPI5 CARDREADER_STINGER CARDREADER_DRECAS
diff --git a/README.md b/README.md
index fea2de8c..23597add 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,18 @@
-OSCam: Open Source Conditional Access Module
-============================================
+OSCam-Emu: Open Source CAM with Emulator
+========================================
+[![build](https://github.com/oscam-mirror/oscam-emu/actions/workflows/build-oscam.yml/badge.svg)](https://github.com/oscam-mirror/oscam-emu/actions/workflows/build-oscam.yml) [![sync](https://github.com/oscam-mirror/oscam-emu/actions/workflows/sync-gitlab.yml/badge.svg)](https://github.com/oscam-mirror/oscam-emu/actions/workflows/sync-gitlab.yml) [![patch](https://github.com/oscam-mirror/oscam-emu/actions/workflows/create-patch.yml/badge.svg)](https://github.com/oscam-mirror/oscam-emu/actions/workflows/create-patch.yml)
 
+Wiki
+====
+https://git.streamboard.tv/common/oscam/-/wikis/home
 
 License
 =======
 
-OSCam: Open Source CAM
-Copyright (C) 2009-2025 OSCam developers
+OSCam-Emu: Open Source CAM with Emulator
+Copyright (C) 2009-2025 OSCam-Emu developers
 
-OSCam is based on the Streamboard mp-cardserver 0.9d by dukat and
+OSCam-Emu is based on the Streamboard mp-cardserver 0.9d by dukat and
 has been extended and worked on by many more since then.
 
 This program is free software: you can redistribute it and/or modify
@@ -24,41 +28,34 @@ GNU General Public License for more details.
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-For the full text of the licese, please read COPYING file in OSCam
+For the full text of the licese, please read COPYING file in OSCam-Emu
 top directory or visit http://www.gnu.org/licenses/gpl-3.0.html
 
 
 Version history
 ===============
 
-OSCam history is accessible through Trac timeline at:
-   https://git.streamboard.tv/common/oscam/-/commits/master
+OSCam-Emu history is accessible through GitHub history at:
+   https://github.com/oscam-mirror/oscam-emu/commits/master/
 
 
 Repositories
 ============
 
 GIT repository:
-   git clone https://git.streamboard.tv/common/oscam.git oscam
+   git clone https://github.com/oscam-mirror/oscam-emu.git
 
 
-Building OSCam from source
+Building OSCam-Emu from source
 ==========================
 
- - Get the lastest sources from SVN
-    git clone https://git.streamboard.tv/common/oscam.git oscam
-
- - Go to oscam-trunk directory.
-
- - Run `make config` to choose the features you want.
-
- - Run `make` to compile OSCam.
+https://git.streamboard.tv/common/oscam/-/wikis/BuildingOscam
 
 For more information and examples on using the build system, please
 see README.build and README.config files.
 
 
-Building OScam for different CPUs (cross-compilation)
+Building OSCam-Emu for different CPUs (cross-compilation)
 =====================================================
 
 First you need to install the target CPU toolchain. Already built toolchains
@@ -66,7 +63,7 @@ for various architectures can be downloaded from:
 
     https://git.streamboard.tv/common/oscam/-/wikis/CrossCompiling
 
-In order to cross compile OSCam you need to set CROSS variable when
+In order to cross compile OSCam-Emu you need to set CROSS variable when
 running make. For example to compile for SH4 architecture you need
 to run: `make CROSS=sh4-linux-` or if your cross compilers are not
 in your PATH - `make CROSS=/opt/STM/STLinux-2.3/devkit/sh4/bin/sh4-linux-`.
@@ -75,11 +72,11 @@ in your PATH - `make CROSS=/opt/STM/STLinux-2.3/devkit/sh4/bin/sh4-linux-`.
 Dependencies
 ============
 
-OSCam by default do not depend on external libraries except when compilation
+OSCam-Emu by default do not depend on external libraries except when compilation
 with SSL is requested. In that case openssl (libcrypto) library must be
 installed.
 
-OSCam supports building with the following external dependencies:
+OSCam-Emu supports building with the following external dependencies:
   - libcrypto (libssl) - 'make USE_LIBCRYPTO=1'
   - libusb             - 'make USE_LIBUSB=1'
   - PCSC               - 'make USE_PCSC=1'
@@ -89,12 +86,10 @@ OSCam supports building with the following external dependencies:
   - AZBOX support      - 'make USE_AZBOX=1'
 
 For STAPI support you need to download liboscam_stapi.a library and place
-it in stapi directory under oscam/ root dir. liboscam_stapi.a can be downloaded
-from: https://board.streamboard.tv
+it in stapi directory under oscam/ root dir.
 
 For STAPI5 support you need to download liboscam_stapi5.a library and place
-it in stapi directory under oscam/ root dir. liboscam_stapi5.a can be downloaded
-from: https://board.streamboard.tv
+it in stapi directory under oscam/ root dir.
 
 For more information and examples on using the build system, run `make help`.
 
@@ -104,12 +99,12 @@ Help and Support
 
 man pages and configuration examples are in Distribution/doc directory.
 
-You may visit our Trac system for tracking and filling bug reports.
-   https://git.streamboard.tv/common/oscam/-/issues/new
+You may visit our GitHub system for tracking and filling bug reports.
+   https://github.com/oscam-mirror/oscam-emu/issues/new
 
 If you experience any problems with OSCam, feel free to post in our support
 forum under (mainly German and English language) at:
-   https://board.streamboard.tv/forum/
+   https://github.com/oscam-mirror/oscam-emu/discussions
 
 Configuration wiki:
-   https://wiki.streamboard.tv/wiki/OSCam
+   https://wiki.streamboard.tv/wiki/OSCam/de/Config/oscam.conf
diff --git a/config.h b/config.h
index f48ca1da..1139180d 100644
--- a/config.h
+++ b/config.h
@@ -1,6 +1,8 @@
 #ifndef CONFIG_H_
 #define CONFIG_H_
 
+#define WITH_EMU 1
+#define WITH_SOFTCAM 1
 #define WEBIF 1
 #define WEBIF_LIVELOG 1
 #define WEBIF_JQUERY 1
@@ -8,7 +10,7 @@
 //#define WITH_SSL 1
 #if defined(__linux__) || defined(__CYGWIN__)
 #define HAVE_DVBAPI 1
-//#define WITH_EXTENDED_CW 1
+#define WITH_EXTENDED_CW 1
 #endif
 //#define WITH_NEUTRINO 1
 #define READ_SDT_CHARSETS 1
diff --git a/config.sh b/config.sh
index 72f2887a..2ad37fc0 100755
--- a/config.sh
+++ b/config.sh
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-addons="WEBIF WEBIF_LIVELOG WEBIF_JQUERY WITH_COMPRESS_WEBIF WITH_SSL HAVE_DVBAPI WITH_EXTENDED_CW WITH_NEUTRINO READ_SDT_CHARSETS CS_ANTICASC WITH_DEBUG MODULE_MONITOR WITH_LB CS_CACHEEX CS_CACHEEX_AIO CW_CYCLE_CHECK LCDSUPPORT LEDSUPPORT CLOCKFIX IPV6SUPPORT WITH_ARM_NEON WITH_SIGNING"
+addons="WEBIF WEBIF_LIVELOG WEBIF_JQUERY WITH_COMPRESS_WEBIF WITH_SSL HAVE_DVBAPI WITH_EXTENDED_CW WITH_NEUTRINO READ_SDT_CHARSETS CS_ANTICASC WITH_DEBUG MODULE_MONITOR WITH_LB CS_CACHEEX CS_CACHEEX_AIO CW_CYCLE_CHECK LCDSUPPORT LEDSUPPORT CLOCKFIX IPV6SUPPORT WITH_ARM_NEON WITH_SIGNING WITH_EMU WITH_SOFTCAM"
 protocols="MODULE_CAMD33 MODULE_CAMD35 MODULE_CAMD35_TCP MODULE_NEWCAMD MODULE_CCCAM MODULE_CCCSHARE MODULE_GBOX MODULE_RADEGAST MODULE_SCAM MODULE_SERIAL MODULE_CONSTCW MODULE_PANDORA MODULE_GHTTP MODULE_STREAMRELAY"
 readers="READER_NAGRA READER_NAGRA_MERLIN READER_IRDETO READER_CONAX READER_CRYPTOWORKS READER_SECA READER_VIACCESS READER_VIDEOGUARD READER_DRE READER_TONGFANG READER_BULCRYPT READER_GRIFFIN READER_DGCRYPT"
 card_readers="CARDREADER_PHOENIX CARDREADER_INTERNAL CARDREADER_SC8IN1 CARDREADER_MP35 CARDREADER_SMARGO CARDREADER_DB2COM CARDREADER_STAPI CARDREADER_STAPI5 CARDREADER_STINGER CARDREADER_DRECAS"
@@ -12,7 +12,7 @@ CONFIG_WEBIF_JQUERY=y
 CONFIG_WITH_COMPRESS_WEBIF=y
 # CONFIG_WITH_SSL=n
 CONFIG_HAVE_DVBAPI=y
-# CONFIG_WITH_EXTENDED_CW=n
+CONFIG_WITH_EXTENDED_CW=y
 # CONFIG_WITH_NEUTRINO=n
 CONFIG_READ_SDT_CHARSETS=y
 # CONFIG_CS_ANTICASC=n
@@ -28,6 +28,8 @@ CONFIG_WITH_LB=y
 # CONFIG_IPV6SUPPORT=n
 # CONFIG_WITH_ARM_NEON=n
 # CONFIG_WITH_SIGNING=n
+CONFIG_WITH_EMU=y
+CONFIG_WITH_SOFTCAM=y
 # CONFIG_MODULE_CAMD33=n
 CONFIG_MODULE_CAMD35=y
 CONFIG_MODULE_CAMD35_TCP=y
@@ -328,14 +330,17 @@ get_opts() {
 
 update_deps() {
 	# Calculate dependencies
-	enabled_any $(get_opts readers) $(get_opts card_readers) && enable_opt WITH_CARDREADER >/dev/null
-	disabled_all $(get_opts readers) $(get_opts card_readers) && disable_opt WITH_CARDREADER >/dev/null
+	enabled_any $(get_opts readers) $(get_opts card_readers) WITH_EMU && enable_opt WITH_CARDREADER >/dev/null
+	disabled_all $(get_opts readers) $(get_opts card_readers) WITH_EMU && disable_opt WITH_CARDREADER >/dev/null
 	disabled WEBIF && disable_opt WEBIF_LIVELOG >/dev/null
 	disabled WEBIF && disable_opt WEBIF_JQUERY >/dev/null
 	enabled MODULE_CCCSHARE && enable_opt MODULE_CCCAM >/dev/null
 	enabled_any CARDREADER_DB2COM CARDREADER_MP35 CARDREADER_SC8IN1 CARDREADER_STINGER && enable_opt CARDREADER_PHOENIX >/dev/null
 	enabled CS_CACHEEX_AIO && enable_opt CS_CACHEEX >/dev/null
 	enabled WITH_SIGNING && enable_opt WITH_SSL >/dev/null
+	enabled WITH_EMU && enable_opt READER_VIACCESS >/dev/null
+	enabled WITH_EMU && enable_opt MODULE_NEWCAMD >/dev/null
+	disabled WITH_EMU && disable_opt WITH_SOFTCAM >/dev/null
 }
 
 list_config() {
@@ -385,9 +390,9 @@ list_config() {
 	not_have_flag USE_LIBCRYPTO && echo "CONFIG_LIB_AES=y" || echo "# CONFIG_LIB_AES=n"
 	enabled MODULE_CCCAM && echo "CONFIG_LIB_RC6=y" || echo "# CONFIG_LIB_RC6=n"
 	not_have_flag USE_LIBCRYPTO && enabled MODULE_CCCAM && echo "CONFIG_LIB_SHA1=y" || echo "# CONFIG_LIB_SHA1=n"
-	enabled_any READER_DRE MODULE_SCAM READER_VIACCESS READER_NAGRA READER_NAGRA_MERLIN READER_VIDEOGUARD READER_CONAX READER_TONGFANG && echo "CONFIG_LIB_DES=y" || echo "# CONFIG_LIB_DES=n"
-	enabled_any MODULE_CCCAM READER_NAGRA READER_NAGRA_MERLIN READER_SECA && echo "CONFIG_LIB_IDEA=y" || echo "# CONFIG_LIB_IDEA=n"
-	not_have_flag USE_LIBCRYPTO && enabled_any READER_CONAX READER_CRYPTOWORKS READER_NAGRA READER_NAGRA_MERLIN && echo "CONFIG_LIB_BIGNUM=y" || echo "# CONFIG_LIB_BIGNUM=n"
+	enabled_any READER_DRE MODULE_SCAM READER_VIACCESS READER_NAGRA READER_NAGRA_MERLIN READER_VIDEOGUARD READER_CONAX READER_TONGFANG WITH_EMU && echo "CONFIG_LIB_DES=y" || echo "# CONFIG_LIB_DES=n"
+	enabled_any MODULE_CCCAM READER_NAGRA READER_NAGRA_MERLIN READER_SECA WITH_EMU && echo "CONFIG_LIB_IDEA=y" || echo "# CONFIG_LIB_IDEA=n"
+	not_have_flag USE_LIBCRYPTO && enabled_any READER_CONAX READER_CRYPTOWORKS READER_NAGRA READER_NAGRA_MERLIN WITH_EMU && echo "CONFIG_LIB_BIGNUM=y" || echo "# CONFIG_LIB_BIGNUM=n"
 	enabled READER_NAGRA_MERLIN && echo "CONFIG_LIB_MDC2=y" || echo "# CONFIG_LIB_MDC2=n"
 	enabled READER_NAGRA_MERLIN && echo "CONFIG_LIB_FAST_AES=y" || echo "# CONFIG_LIB_FAST_AES=n"
 	enabled_any READER_NAGRA_MERLIN WITH_SIGNING && echo "CONFIG_LIB_SHA256=y" || echo "# CONFIG_LIB_SHA256=n"
@@ -515,6 +520,8 @@ menu_addons() {
 		IPV6SUPPORT			"IPv6 support (experimental)"					$(check_test "IPV6SUPPORT") \
 		WITH_ARM_NEON		"ARM NEON (SIMD/MPE) support"					$(check_test "WITH_ARM_NEON") \
 		WITH_SIGNING		"Binary signing with X.509 certificate"			$(check_test "WITH_SIGNING") \
+		WITH_EMU			"Emulator support"								$(check_test "WITH_EMU") \
+		WITH_SOFTCAM		"Built-in SoftCam.Key"							$(check_test "WITH_SOFTCAM") \
 		2> ${tempfile}
 
 	opt=${?}
@@ -897,11 +904,12 @@ do
 	;;
 	'-v'|'--oscam-version')
 		version=`grep '^#define CS_VERSION' globals.h | cut -d\" -f2`
-		echo $version
+		emuversion=`grep EMU_VERSION module-emulator-osemu.h | awk '{ print $3 }'`
+		echo $version-$emuversion
 		break
 	;;
 	'-c'|'--oscam-commit')
-		sha=`git log 2>/dev/null | sed -n 1p | cut -d ' ' -f2 | cut -c1-8`
+		sha=`git log --no-merges 2>/dev/null | sed -n 1p | cut -d ' ' -f2 | cut -c1-8`
 		echo $sha
 		break
 	;;
diff --git a/csctapi/cardreaders.h b/csctapi/cardreaders.h
index 8b6c309a..35f38c6c 100644
--- a/csctapi/cardreaders.h
+++ b/csctapi/cardreaders.h
@@ -14,5 +14,6 @@ extern const struct s_cardreader cardreader_smartreader;
 extern const struct s_cardreader cardreader_stapi;
 extern const struct s_cardreader cardreader_stinger;
 extern const struct s_cardreader cardreader_drecas;
+extern const struct s_cardreader cardreader_emu;
 
 #endif
diff --git a/globals.h b/globals.h
index e1b3d7f6..45a98092 100644
--- a/globals.h
+++ b/globals.h
@@ -368,9 +368,9 @@
 /* ===========================
  *			constants
  * =========================== */
-#define SCM_URL					"https://git.streamboard.tv/common/oscam"
+#define SCM_URL					"https://github.com/oscam-mirror/oscam-emu"
 #define WIKI_URL				"https://wiki.streamboard.tv/wiki"
-#define BOARD_URL				"https://board.streamboard.tv"
+#define BOARD_URL				"https://github.com/oscam-mirror/oscam-emu/discussions"
 #ifndef CS_VERSION
 #define CS_VERSION				"2.25.05-11884"
 #endif
@@ -406,14 +406,18 @@
 #define CS_ECM_RINGBUFFER_MAX	0x10	// max size for ECM last responsetimes ringbuffer. Keep this set to power of 2 values!
 
 // Support for multiple CWs per channel and other encryption algos
-//#define WITH_EXTENDED_CW		1
+#define WITH_EXTENDED_CW		1
 
 #define MAX_ECM_SIZE			1024
 #define MAX_EMM_SIZE			1024
 
 #define MAX_CMD_SIZE 0xff + 5  // maximum value from length byte + command header
 
+#ifdef WITH_EMU
+#define CS_EMMCACHESIZE			1024	// nr of EMMs that EMU reader will cache
+#else
 #define CS_EMMCACHESIZE			512		// nr of EMMs that each reader will cache
+#endif
 #define MSGLOGSIZE				64		// size of string buffer for a ecm to return messages
 
 #define D_TRACE					0x0001	// Generate very detailed error/trace messages per routine
@@ -452,6 +456,7 @@
 #define R_SMART					0x7		// Smartreader+
 #define R_PCSC					0x8		// PCSC
 #define R_DRECAS				0x9		// Reader DRECAS
+#define R_EMU					0x17	// Reader EMU
 /////// proxy readers after R_CS378X
 #define R_CAMD35				0x20	// Reader cascading camd 3.5x
 #define R_CAMD33				0x21	// Reader cascading camd 3.3x
@@ -889,6 +894,13 @@ typedef struct s_entitlement						// contains entitlement Info
 	uint32_t		class;							// the class needed for some systems
 	time_t			start;							// startdate
 	time_t			end;							// enddate
+#ifdef WITH_EMU
+	bool			isKey;
+	bool			isData;
+	char			name[8];
+	uint8_t			*key;
+	uint32_t		keyLength;
+#endif
 } S_ENTITLEMENT;
 
 struct s_client;
@@ -1756,6 +1768,7 @@ struct s_reader										// contains device info, reader info and card info
 	char			cc_build[7];					// cccam build number
 	int8_t			cc_maxhops;						// cccam max distance
 	int8_t			cc_mindown;						// cccam min downhops
+	int8_t			cc_want_emu;					// Schlocke: Client want to have EMUs, 0 - NO; 1 - YES
 	uint32_t		cc_id;
 	int8_t			cc_keepalive;
 	int8_t			cc_hop;							// For non-cccam reader: hop for virtual cards
@@ -1932,6 +1945,11 @@ struct s_reader										// contains device info, reader info and card info
 #endif
 #ifdef MODULE_GHTTP
 	uint8_t			ghttp_use_ssl;
+#endif
+#ifdef WITH_EMU
+	FTAB			emu_auproviders;				// AU providers for Emu reader
+	int8_t			emu_datecodedenabled;			// date-coded keys for BISS
+	LLIST			*ll_biss2_rsa_keys;				// BISS2 RSA keys - Read from external PEM files
 #endif
 	uint8_t			cnxlastecm;						// == 0 - last ecm has not been paired ecm, > 0 last ecm has been paired ecm
 	LLIST			*emmstat;						// emm stats
@@ -2465,6 +2483,10 @@ struct s_config
 #else
 #define DEFAULT_STREAM_SOURCE_PORT 8001 //Enigma2
 #endif
+#endif
+#ifdef WITH_EMU
+	uint32_t		emu_stream_ecm_delay;
+	int8_t			emu_stream_emm_enabled;
 #endif
 
 	int32_t			max_cache_time;					// seconds ecms are stored in ecmcwcache
@@ -2650,12 +2672,18 @@ bool boxtype_is(const char *boxtype);
 bool boxname_is(const char *boxname);
 const char *boxtype_get(void);
 const char *boxname_get(void);
+static inline bool caid_is_fake(uint16_t caid) { return caid == 0xffff; }
+static inline bool caid_is_biss(uint16_t caid) { return caid >> 8 == 0x26; }
+static inline bool caid_is_biss_fixed(uint16_t caid) { return caid == 0x2600 || caid == 0x2602; } // fixed cw, fake ecm
+static inline bool caid_is_biss_dynamic(uint16_t caid) { return caid == 0x2610; } // dynamic cw, real ecm and emm
 static inline bool caid_is_seca(uint16_t caid) { return caid >> 8 == 0x01; }
 static inline bool caid_is_viaccess(uint16_t caid) { return caid >> 8 == 0x05; }
 static inline bool caid_is_irdeto(uint16_t caid) { return caid >> 8 == 0x06; }
 static inline bool caid_is_videoguard(uint16_t caid) { return caid >> 8 == 0x09; }
 static inline bool caid_is_conax(uint16_t caid) { return caid >> 8 == 0x0B; }
 static inline bool caid_is_cryptoworks(uint16_t caid) { return caid >> 8 == 0x0D; }
+static inline bool caid_is_powervu(uint16_t caid) { return caid >> 8 == 0x0E; }
+static inline bool caid_is_director(uint16_t caid) { return caid >> 8 == 0x10; }
 static inline bool caid_is_betacrypt(uint16_t caid) { return caid >> 8 == 0x17; }
 static inline bool caid_is_nagra(uint16_t caid) { return caid >> 8 == 0x18; }
 static inline bool caid_is_bulcrypt(uint16_t caid) { return caid == 0x5581 || caid == 0x4AEE; }
@@ -2663,4 +2691,8 @@ static inline bool caid_is_dre(uint16_t caid) { return caid == 0x4AE0 || caid ==
 static inline bool caid_is_tongfang(uint16_t caid) { return caid == 0x4A02; }
 const char *get_cardsystem_desc_by_caid(uint16_t caid);
 
+#ifdef WITH_EMU
+FILTER *get_emu_prids_for_caid(struct s_reader *rdr, uint16_t caid);
+#endif
+
 #endif
diff --git a/module-cacheex.c b/module-cacheex.c
index 9287aed3..04e27699 100644
--- a/module-cacheex.c
+++ b/module-cacheex.c
@@ -921,7 +921,9 @@ static int32_t cacheex_add_to_cache_int(struct s_client *cl, ECM_REQUEST *er, in
 	}
 #endif
 
-	if(chk_is_null_CW(er->cw))
+	// Skip check for BISS1 - cw could be indeed zero
+	// Skip check for BISS2 - we use the extended cw, so the "simple" cw is always zero
+	if(chk_is_null_CW(er->cw) && !caid_is_biss(er->caid))
 	{
 		cs_log_dump_dbg(D_CACHEEX, er->cw, 16, "push received null cw from %s", csp ? "csp" : username(cl));
 		cl->cwcacheexerr++;
@@ -930,7 +932,9 @@ static int32_t cacheex_add_to_cache_int(struct s_client *cl, ECM_REQUEST *er, in
 		return 0;
 	}
 
-	if(get_odd_even(er) == 0)
+	// Don't check for BISS1 and BISS2 mode 1/E or fake caid (ECM is fake for them)
+	// Don't check for BISS2 mode CA (ECM table is always 0x80)
+	if(!caid_is_biss(er->caid) && !caid_is_fake(er->caid) && get_odd_even(er) == 0)
 	{
 		cs_log_dbg(D_CACHEEX, "push received ecm with null odd/even byte from %s", csp ? "csp" : username(cl));
 		cl->cwcacheexerr++;
diff --git a/module-camd35-cacheex.c b/module-camd35-cacheex.c
index 68c24d75..f7258a02 100644
--- a/module-camd35-cacheex.c
+++ b/module-camd35-cacheex.c
@@ -670,7 +670,7 @@ void camd35_cacheex_feature_trigger(struct s_client *cl, int32_t feature, uint8_
 			if(size < 32)
 				size = 32;
 
-			uint8_t token[14];
+			uint8_t token[18];
 
 			// bitfield
 			i2b_buf(2, feature, payload + i);
diff --git a/module-cccam-cacheex.c b/module-cccam-cacheex.c
index 6878d327..5acc7b9a 100644
--- a/module-cccam-cacheex.c
+++ b/module-cccam-cacheex.c
@@ -660,7 +660,7 @@ void cc_cacheex_feature_trigger(struct s_client *cl, int32_t feature, uint8_t mo
 			break;
 		// aio-version
 		case 32: ;
-			uint8_t token[14];
+			uint8_t token[18];
 
 			// bitfield
 			i2b_buf(2, feature, payload + i);
diff --git a/module-cccam.c b/module-cccam.c
index c89815c8..febbbad0 100644
--- a/module-cccam.c
+++ b/module-cccam.c
@@ -1009,7 +1009,7 @@ int32_t cc_send_cli_data(struct s_client *cl)
 
 	memcpy(buf, rdr->r_usr, sizeof(rdr->r_usr));
 	memcpy(buf + 20, cc->node_id, 8);
-	buf[28] = 0; // <-- Client wants to have EMUs, 0 - NO; 1 - YES
+	buf[28] = rdr->cc_want_emu; // <-- Client wants to have EMUs, 0 - NO; 1 - YES
 	memcpy(buf + 29, rdr->cc_version, sizeof(rdr->cc_version)); // cccam version (ascii)
 	memcpy(buf + 61, rdr->cc_build, sizeof(rdr->cc_build)); // build number (ascii)
 
@@ -1544,7 +1544,8 @@ struct cc_card *get_matching_card(struct s_client *cl, ECM_REQUEST *cur_er, int8
 		}
 
 		if((ncard->caid == cur_er->caid // caid matches
-			|| lb_match)) // or system matches if caid ends with 00
+			|| (rdr->cc_want_emu && (ncard->caid == (cur_er->caid & 0xFF00))))
+			|| lb_match) // or system matches if caid ends with 00 (needed for wantemu)
 		{
 			int32_t goodSidCount = ll_count(ncard->goodsids);
 			int32_t badSidCount = ll_count(ncard->badsids);
@@ -1583,7 +1584,7 @@ struct cc_card *get_matching_card(struct s_client *cl, ECM_REQUEST *cur_er, int8
 				}
 			}
 
-			if(caid_is_nagra(ncard->caid) && (!xcard || ncard->hop < xcard->hop))
+			if(!(rdr->cc_want_emu) && caid_is_nagra(ncard->caid) && (!xcard || ncard->hop < xcard->hop))
 			{
 				xcard = ncard; // remember card (D+ / 1810 fix) if request has no provider, but card has
 			}
diff --git a/module-cw-cycle-check.c b/module-cw-cycle-check.c
index 5a99d325..5e6dda5f 100644
--- a/module-cw-cycle-check.c
+++ b/module-cw-cycle-check.c
@@ -123,7 +123,9 @@ static uint8_t checkvalidCW(ECM_REQUEST *er)
 {
 	uint8_t ret = 1;
 
-	if(chk_is_null_CW(er->cw))
+	// Skip check for BISS1 - cw could be indeed zero
+	// Skip check for BISS2 - we use the extended cw, so the "simple" cw is always zero
+	if(chk_is_null_CW(er->cw) && !caid_is_biss(er->caid))
 	{ er->rc = E_NOTFOUND; }
 
 	if(er->rc == E_NOTFOUND)
diff --git a/module-dvbapi-azbox.c b/module-dvbapi-azbox.c
index 7387f02d..321f19e8 100644
--- a/module-dvbapi-azbox.c
+++ b/module-dvbapi-azbox.c
@@ -312,7 +312,9 @@ void azbox_send_dcw(struct s_client *client, ECM_REQUEST *er)
 	int32_t n;
 	for(n = 0; n < 2; n++)
 	{
-		if(memcmp(er->cw + (n * 8), demux[0].last_cw[0][n], 8) && (memcmp(er->cw + (n * 8), nullcw, 8) != 0))
+		// Skip check for BISS1 - cw could be indeed zero
+		// Skip check for BISS2 - we use the extended cw, so the "simple" cw is always zero
+		if(memcmp(er->cw + (n * 8), demux[0].last_cw[0][n], 8) && (memcmp(er->cw + (n * 8), nullcw, 8) != 0 || caid_is_biss(er->caid)))
 		{
 			memcpy(demux[0].last_cw[0][n], er->cw + (n * 8), 8);
 			memcpy(openxcas_cw + (n * 8), er->cw + (n * 8), 8);
diff --git a/module-dvbapi-mca.c b/module-dvbapi-mca.c
index 8b4d44d5..cc399e40 100644
--- a/module-dvbapi-mca.c
+++ b/module-dvbapi-mca.c
@@ -601,8 +601,10 @@ void mca_send_dcw(struct s_client *client, ECM_REQUEST *er)
 	int32_t n;
 	for(n = 0; n < 2; n++)
 	{
+		// Skip check for BISS1 - cw could be indeed zero
+		// Skip check for BISS2 - we use the extended cw, so the "simple" cw is always zero
 		if((memcmp(er->cw + (n * 8), demux[0].last_cw[0][0], 8) && memcmp(er->cw + (n * 8), demux[0].last_cw[0][1], 8))
-			&& (memcmp(er->cw + (n * 8), nullcw, 8) != 0))
+			&& (memcmp(er->cw + (n * 8), nullcw, 8) != 0 || caid_is_biss(er->caid)))
 		{
 			memcpy(demux[0].last_cw[0][n], er->cw + (n * 8), 8);
 			memcpy(openxcas_cw + (n * 8), er->cw + (n * 8), 8);
diff --git a/module-dvbapi-stapi.c b/module-dvbapi-stapi.c
index e136091d..2264bfc2 100644
--- a/module-dvbapi-stapi.c
+++ b/module-dvbapi-stapi.c
@@ -730,8 +730,10 @@ int32_t stapi_write_cw(int32_t demux_id, uint8_t *cw, uint16_t *STREAMpids, int3
 
 	for(l = 0; l < 2; l++)
 	{
+		// Skip check for BISS1 - cw could be indeed zero
+		// Skip check for BISS2 - we use the extended cw, so the "simple" cw is always zero
 		if(memcmp(cw + (l * 8), demux[demux_id].last_cw[0][l], 8) != 0
-			&& (memcmp(cw + (l * 8), nullcw, 8) != 0))
+			&& (memcmp(cw + (l * 8), nullcw, 8) != 0 || caid_is_biss(demux[demux_id].ECMpids[pidnum].CAID)))
 		{
 			for(n = 0; n < PTINUM; n++)
 			{
diff --git a/module-dvbapi-stapi5.c b/module-dvbapi-stapi5.c
index 2adf4518..76f0a6ff 100644
--- a/module-dvbapi-stapi5.c
+++ b/module-dvbapi-stapi5.c
@@ -731,8 +731,10 @@ int32_t stapi_write_cw(int32_t demux_id, uint8_t *cw, uint16_t *STREAMpids, int3
 
 	for(l = 0; l < 2; l++)
 	{
+		// Skip check for BISS1 - cw could be indeed zero
+		// Skip check for BISS2 - we use the extended cw, so the "simple" cw is always zero
 		if(memcmp(cw + (l * 8), demux[demux_id].last_cw[0][l], 8) != 0
-			&& (memcmp(cw + (l * 8), nullcw, 8) != 0))
+			&& (memcmp(cw + (l * 8), nullcw, 8) != 0 || caid_is_biss(demux[demux_id].ECMpids[pidnum].CAID)))
 		{
 			ErrorCode = oscam_sttkd_KeyWrite(tkd_desc_info[demux[demux_id].dev_index].key_hndl, l, cw + (l * 8));
 
diff --git a/module-dvbapi.c b/module-dvbapi.c
index 894e414e..c1f6109b 100644
--- a/module-dvbapi.c
+++ b/module-dvbapi.c
@@ -1883,7 +1883,14 @@ void dvbapi_start_emm_filter(int32_t demux_id)
 
 			if(match)
 			{
-				csystem = get_cardsystem_by_caid(caid);
+				if(rdr->typ == R_EMU)
+				{
+					csystem = rdr->csystem;
+				}
+				else
+				{
+					csystem = get_cardsystem_by_caid(caid);
+				}
 				if(csystem)
 				{
 					if(caid != ncaid)
@@ -1904,7 +1911,15 @@ void dvbapi_start_emm_filter(int32_t demux_id)
 					}
 					else if(csystem->get_emm_filter)
 					{
-						csystem->get_emm_filter(rdr, &dmx_filter, &filter_count);
+						if(rdr->typ == R_EMU)
+						{
+							csystem->get_emm_filter_adv(rdr, &dmx_filter, &filter_count, caid, provid, demux[demux_id].program_number,
+														demux[demux_id].tsid, demux[demux_id].onid, demux[demux_id].ens);
+						}
+						else
+						{
+							csystem->get_emm_filter(rdr, &dmx_filter, &filter_count);
+						}
 					}
 				}
 				else
@@ -2642,6 +2657,8 @@ int32_t dvbapi_start_descrambling(int32_t demux_id, int32_t pid, int8_t checked,
 	er->vpid   = demux[demux_id].ECMpids[pid].VPID;
 	er->pmtpid = demux[demux_id].pmtpid;
 	er->onid   = demux[demux_id].onid;
+	er->tsid   = demux[demux_id].tsid;
+	er->ens    = demux[demux_id].ens;
 	er->msgid  = msgid;
 
 #ifdef WITH_STAPI5
@@ -2682,6 +2699,63 @@ int32_t dvbapi_start_descrambling(int32_t demux_id, int32_t pid, int8_t checked,
 			match = 1; // so make it a match to try it!
 		}
 
+		// BISS1 and BISS2 mode 1/E or FAKE caid
+		// ecm pid is fake, so send out one fake ecm request
+		// special treatment: if we asked the cw first without starting a filter,
+		// the cw request will be killed due to no ecmfilter started
+		if(caid_is_fake(demux[demux_id].ECMpids[pid].CAID) || caid_is_biss_fixed(demux[demux_id].ECMpids[pid].CAID))
+		{
+			int32_t j, n;
+			er->ecmlen = 7;
+			er->ecm[0] = 0x80; // to pass the cache check it must be 0x80 or 0x81
+			er->ecm[1] = 0x00;
+			er->ecm[2] = 0x04;
+			i2b_buf(2, er->srvid, er->ecm + 3);
+			i2b_buf(2, er->pmtpid, er->ecm + 5);
+
+			for(j = 0, n = 7; j < demux[demux_id].STREAMpidcount; j++, n += 2)
+			{
+				i2b_buf(2, demux[demux_id].STREAMpids[j], er->ecm + n);
+				er->ecm[2] += 2;
+				er->ecmlen += 2;
+			}
+
+			er->ens &= 0x0FFFFFFF; // clear top 4 bits (in case of DVB-T/C or garbage), prepare for flagging
+			er->ens |= 0xA0000000; // flag to emu: this is the namespace, not a pid
+
+			i2b_buf(2, er->tsid, er->ecm + er->ecmlen);     // place tsid after the last stream pid
+			i2b_buf(2, er->onid, er->ecm + er->ecmlen + 2); // place onid right after tsid
+			i2b_buf(4, er->ens, er->ecm + er->ecmlen + 4);  // place namespace at the end of the ecm
+
+			er->ecm[2] += 8;
+			er->ecmlen += 8;
+
+			cs_log("Demuxer %d trying to descramble PID %d CAID %04X PROVID %06X ECMPID %04X ANY CHID PMTPID %04X VPID %04X",
+				demux_id,
+				pid,
+				demux[demux_id].ECMpids[pid].CAID,
+				demux[demux_id].ECMpids[pid].PROVID,
+				demux[demux_id].ECMpids[pid].ECM_PID,
+				demux[demux_id].pmtpid,
+				demux[demux_id].ECMpids[pid].VPID);
+
+			demux[demux_id].curindex = pid; // set current pid to the fresh started one
+			dvbapi_start_filter(demux_id,
+						pid,
+						demux[demux_id].ECMpids[pid].ECM_PID,
+						demux[demux_id].ECMpids[pid].CAID,
+						demux[demux_id].ECMpids[pid].PROVID,
+						0x80,
+						0xF0,
+						3000,
+						TYPE_ECM);
+
+			started = 1;
+			request_cw(dvbapi_client, er, demux_id, 0); // do not register ecm since this try!
+			fake_ecm = 1;
+			break; // we started an ecmfilter so stop looking for next matching reader!
+		}
+
 		if(match) // if matching reader found check for irdeto cas if local irdeto card check if it received emms in last 60 minutes
 		{
 			if(caid_is_irdeto(er->caid)) // irdeto cas init irdeto_curindex to wait for first index (00)
@@ -3791,7 +3865,7 @@ static void dvbapi_parse_pmt_ca_descriptor(int32_t demux_id, const uint8_t *buff
 	ca_system_id = b2i(2, buffer);
 	ca_pid = b2i(2, buffer + 2) & 0x1FFF;
 
-	if(ca_system_id == 0x0000 || ca_pid == 0x1FFF)
+	if(ca_system_id == 0x0000 || (!caid_is_biss_fixed(ca_system_id) && !caid_is_fake(ca_system_id) && ca_pid == 0x1FFF))
 	{
 		return; // This is not a valid CAID or ECM pid
 	}
@@ -4379,14 +4453,33 @@ static void dvbapi_capmt_notify(struct demux_s *dmx)
 
 static void dvbapi_prepare_descrambling(int32_t demux_id, uint32_t msgid)
 {
-	bool start_emm = true;
+	bool is_powervu = false, start_emm = true;
 	char service_name[CS_SERVICENAME_SIZE];
 
+	// The CA PMT should have given us enough info to determine if descrambling
+	// is possible. Parsing the (real) PMT is not necessary, unless we have a
+	// PowerVu encrypted channel or (for some weird reason) no stream pids at all.
+	// Actually, when no streams are available, we set the PMT pid as the 1st
+	// stream pid, so we have to check against that. Finally, if the PMT pid is
+	// not included in the CA PMT, we start the PAT filter instead.
+
+#ifdef WITH_EXTENDED_CW
+	uint8_t i;
+	for(i = 0; i < demux[demux_id].ECMpidcount; i++)
+	{
+		if(caid_is_powervu(demux[demux_id].ECMpids[i].CAID))
+		{
+			is_powervu = true;
+			break;
+		}
+	}
+#endif
+
 	if(demux[demux_id].pmtpid == 0)
 	{
 		dvbapi_start_pat_filter(demux_id);
 	}
-	else if(demux[demux_id].STREAMpids[0] == demux[demux_id].pmtpid)
+	else if(demux[demux_id].STREAMpids[0] == demux[demux_id].pmtpid || is_powervu)
 	{
 		dvbapi_start_pmt_filter(demux_id);
 	}
@@ -5586,6 +5679,7 @@ void dvbapi_process_input(int32_t demux_id, int32_t filter_num, uint8_t *buffer,
 	if(filtertype == TYPE_ECM)
 	{
 		uint32_t chid = 0x10000;
+		int8_t pvu_skip = 0;
 		ECM_REQUEST *er;
 
 		if(len != 0) // len = 0 receiver encountered an internal bufferoverflow!
@@ -5615,8 +5709,25 @@ void dvbapi_process_input(int32_t demux_id, int32_t filter_num, uint8_t *buffer,
 				return;
 			}
 
+#ifdef WITH_EMU
+			if(caid_is_powervu(curpid->CAID)) // ecm counter for powervu
+			{
+				pvu_skip = 1;
+
+				if(sctlen - 11 > buffer[9])
+				{
+					if(buffer[11 + buffer[9]] > curpid->pvu_counter
+						|| (curpid->pvu_counter == 255 && buffer[11 + buffer[9]] == 0)
+						|| ((curpid->pvu_counter - buffer[11 + buffer[9]]) > 5))
+					{
+						curpid->pvu_counter = buffer[11 + buffer[9]];
+						pvu_skip = 0;
+					}
+				}
+			}
+#endif
 			// wait for odd / even ecm change (only not for irdeto!)
-			if(curpid->table == buffer[0] && !caid_is_irdeto(curpid->CAID))
+			if((curpid->table == buffer[0] && !caid_is_irdeto(curpid->CAID)) || pvu_skip)
 			{
 				if(!(er = get_ecmtask()))
 				{
@@ -5960,6 +6071,35 @@ void dvbapi_process_input(int32_t demux_id, int32_t filter_num, uint8_t *buffer,
 			return; // just skip on internal buffer overflow
 		}
 
+#ifdef WITH_EMU
+		if(caid_is_director(demux[demux_id].demux_fd[filter_num].caid))
+		{
+			uint32_t i;
+			uint32_t emmhash;
+
+			if(sctlen < 4)
+			{
+				return;
+			}
+
+			for(i = 0; i + 2 < sctlen; i++)
+			{
+				if(buffer[i] == 0xF0 && (buffer[i + 2] == 0xE1 || buffer[i + 2] == 0xE4))
+				{
+					emmhash = (buffer[3] << 8) | buffer[sctlen - 2];
+					if(demux[demux_id].demux_fd[filter_num].cadata == emmhash)
+					{
+						return;
+					}
+
+					demux[demux_id].demux_fd[filter_num].cadata = emmhash;
+					dvbapi_process_emm(demux_id, filter_num, buffer, sctlen);
+					return;
+				}
+			}
+			return;
+		}
+#endif
 		// fix to handle more than one irdeto emm packet
 		uint8_t *pbuf = buffer;
 		int32_t done = 0;
@@ -7178,8 +7318,10 @@ void dvbapi_write_cw(int32_t demux_id, int32_t pid, int32_t stream_id, uint8_t *
 	for(n = 0; n < 2; n++)
 	{
 		// Check if cw has changed and if new cw is empty (all zeros)
+		// Skip check for BISS1 - cw could be indeed zero
+		// Skip check for BISS2 - we use the extended cw, so the "simple" cw is always zero
 		if((memcmp(cw + (n * cw_length), demux[demux_id].last_cw[stream_id][n], cw_length) != 0 || cw_empty)
-			&& (memcmp(cw + (n * cw_length), null_cw, cw_length) != 0))
+			&& (memcmp(cw + (n * cw_length), null_cw, cw_length) != 0 || caid_is_biss(demux[demux_id].ECMpids[pid].CAID)))
 		{
 			// prepare ca device
 			uint32_t idx = dvbapi_ca_set_pid(demux_id, pid, stream_id, (algo == CA_ALGO_DES), msgid);
@@ -7490,7 +7632,9 @@ void dvbapi_send_dcw(struct s_client *client, ECM_REQUEST *er)
 
 		// 0=matching ecm hash, 2=no filter, 3=table reset, 4=cache-ex response
 		// Check only against last_cw[0] (index 0) - No need to check the rest
-		if((status == 0 || status == 3 || status == 4) && er->rc < E_NOTFOUND)
+		// Skip check for BISS1 - cw could be indeed zero
+		// Skip check for BISS2 - we use the extended cw, so the "simple" cw is always zero
+		if((status == 0 || status == 3 || status == 4) && er->rc < E_NOTFOUND && !caid_is_biss(er->caid))
 		{
 			// check for matching control word
 			if(memcmp(er->cw, demux[i].last_cw[0][0], 8) == 0 &&
@@ -8495,7 +8639,9 @@ int32_t dvbapi_check_ecm_delayed_delivery(int32_t demux_id, ECM_REQUEST *er)
 	}
 
 	// Check for null cw
-	if(memcmp(er->cw, nullcw, 8) == 0 && memcmp(er->cw + 8, nullcw, 8) == 0)
+	// Skip check for BISS1 - cw could be indeed zero
+	// Skip check for BISS2 - we use the extended cw, so the "simple" cw is always zero
+	if(memcmp(er->cw, nullcw, 8) == 0 && memcmp(er->cw + 8, nullcw, 8) == 0 && !caid_is_biss(er->caid))
 	{
 		return 5;
 	}
diff --git a/module-dvbapi.h b/module-dvbapi.h
index 12e9dab5..86eae695 100644
--- a/module-dvbapi.h
+++ b/module-dvbapi.h
@@ -336,6 +336,9 @@ typedef struct filter_s
 	uint32_t         SlotHandle[10];
 	uint32_t         BufferHandle[10];
 #endif
+#ifdef WITH_EMU
+	uint32_t cadata;
+#endif
 } FILTERTYPE;
 
 #ifdef WITH_EXTENDED_CW
@@ -368,6 +371,9 @@ typedef struct s_ecmpid
 	uint32_t         index[MAX_STREAM_INDICES];          // ca indices used for this ecm pid (index[0] holds ca index for STREAMmpids[0] and so on)
 	uint32_t         streams;                            // bit mask of STREAMpids enabled for this ECMpid
 	uint32_t         cadata;
+#ifdef WITH_EMU
+	int16_t pvu_counter;
+#endif
 } ECMPIDTYPE;
 
 typedef struct s_emmpid
diff --git a/module-emulator-biss.c b/module-emulator-biss.c
new file mode 100644
index 00000000..a6b0c9e6
--- /dev/null
+++ b/module-emulator-biss.c
@@ -0,0 +1,883 @@
+#define MODULE_LOG_PREFIX "emu"
+
+#include "globals.h"
+
+#ifdef WITH_EMU
+
+#include "module-emulator-osemu.h"
+#include "module-emulator-biss.h"
+#include "oscam-aes.h"
+#include "oscam-string.h"
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+//#include <openssl/rsa.h>
+#include <openssl/x509.h>
+
+// DVB-CISSA v1 IV as defined in ETSI TS 103 127
+static const uint8_t dvb_cissa_iv[16] =
+{
+	0x44, 0x56, 0x42, 0x54, 0x4D, 0x43, 0x50, 0x54,
+	0x41, 0x45, 0x53, 0x43, 0x49, 0x53, 0x53, 0x41
+};
+
+static void unify_orbitals(uint32_t *namespace)
+{
+	// Unify orbitals to produce same namespace among users
+	// Set positions according to http://satellites-xml.org
+
+	uint16_t pos = (*namespace & 0x0FFF0000) >> 16;
+
+	switch (pos)
+	{
+		case 29: // Rascom QAF 1R
+		case 31: // Eutelsat 3B
+		{
+			pos = 30;
+			break;
+		}
+
+		case 49:
+		case 50: // SES 5
+		{
+			pos = 48; // Astra 4A
+			break;
+		}
+
+		case 215:
+		{
+			pos = 216; // Eutelsat 21B
+			break;
+		}
+
+		case 285: // Astra 2E
+		{
+			pos = 282; // Astra 2F/2G
+			break;
+		}
+
+		case 328: // Intelsat 28
+		case 329:
+		case 331: // Eutelsat 33C
+		{
+			pos = 330;
+			break;
+		}
+
+		case 359: // Eutelsat 36B
+		case 361: // Express AMU1
+		{
+			pos = 360;
+			break;
+		}
+
+		case 451: // Intelsat 904
+		{
+			pos = 450; // Intelsat 12
+			break;
+		}
+
+		case 550:
+		case 551: // G-Sat 8/16
+		{
+			pos = 549; // Yamal 402
+			break;
+		}
+
+		case 748:
+		case 749: // ABS 2A
+		{
+			pos = 750;
+			break;
+		}
+
+		case 848: // Horizons 2
+		case 852: // Intelsat 15
+		{
+			pos = 850;
+			break;
+		}
+
+		case 914: // Mesasat 3a
+		{
+			pos = 915; // Mesasat 3/3b
+			break;
+		}
+
+		case 934: // G-Sat 17
+		case 936: // Insat 4B
+		{
+			pos = 935; // G-Sat 15
+			break;
+		}
+
+		case 3600 - 911: // Nimiq 6
+		{
+			pos = 3600 - 910; // Galaxy 17
+			break;
+		}
+
+		case 3600 - 870: // SES 2
+		case 3600 - 872: // TKSat 1
+		{
+			pos = 3600 - 871;
+			break;
+		}
+
+		case 3600 - 432: // Sky Brasil 1
+		case 3600 - 430: // Intelsat 11
+		{
+			pos = 3600 - 431;
+			break;
+		}
+
+		case 3600 - 376: // Telstar 11N
+		case 3600 - 374: // NSS 10
+		{
+			pos = 3600 - 375;
+			break;
+		}
+
+		case 3600 - 359: // Hispasat 36W-1
+		{
+			pos = 3600 - 360; // Eutelsat 36 West A
+			break;
+		}
+
+		case 3600 - 81: // Eutelsat 8 West B
+		{
+			pos = 3600 - 80;
+			break;
+		}
+
+		case 3600 - 73: // Eutelsat 7 West A
+		case 3600 - 72:
+		case 3600 - 71:
+		{
+			pos = 3600 - 70; // Nilesat 201
+			break;
+		}
+
+		case 3600 - 10: // Intelsat 10-02
+		case 3600 - 9: // Thor 6
+		case 3600 - 7: // Thor 7
+		case 3600 - 6: // Thor 7
+		{
+			pos = 3600 - 8; // Thor 5
+			break;
+		}
+	}
+
+	*namespace = (*namespace & 0xF000FFFF) | (pos << 16);
+}
+
+static void annotate(char *buf, uint8_t len, const uint8_t *ecm, uint16_t ecmLen,
+						uint32_t hash, int8_t isNamespaceHash, int8_t datecoded)
+{
+	// Extract useful information to append to the "Example key ..." message.
+	//
+	// For feeds, the orbital position & frequency are usually embedded in the namespace.
+	// See https://github.com/OpenPLi/enigma2/blob/develop/lib/dvb/frontend.cpp#L476
+	// hash = (sat.orbital_position << 16);
+	// hash |= ((sat.frequency/1000)&0xFFFF)|((sat.polarisation&1) << 15);
+	//
+	// If the onid & tsid appear to be a unique DVB identifier, enigma2 strips the frequency
+	// from our namespace. See https://github.com/OpenPLi/enigma2/blob/develop/lib/dvb/scan.cpp#L55
+	// In that case, our annotation contains the onid:tsid:sid triplet in lieu of frequency.
+	//
+	// For the universal case, we print the number of elementary stream pids & pmtpid.
+	// The sid and current time are included for all. Examples:
+	//
+	// F 1A2B3C4D 00000000 XXXXXXXXXXXXXXXX ; 110.5W 12345H sid:0001 added: 2017-10-17 @ 13:14:15 // namespace
+	// F 1A2B3C4D 20180123 XXXXXXXXXXXXXXXX ;  33.5E  ABCD:9876:1234 added: 2017-10-17 @ 13:14:15 // stripped namespace
+	// F 1A2B3C4D 20180123 XXXXXXXXXXXXXXXX ; av:5 pmt:0134 sid:0001 added: 2017-10-17 @ 13:14:15 // universal
+
+	uint8_t pidcount;
+	uint16_t frequency, degrees, pmtpid, srvid, tsid, onid;
+	uint32_t ens;
+	char compass, polarisation, timeStr1[9], timeStr2[19];
+
+	if (datecoded)
+	{
+		date_to_str(timeStr1, sizeof(timeStr1), 4, 3);
+	}
+	else
+	{
+		snprintf(timeStr1, sizeof(timeStr1), "00000000");
+	}
+
+	date_to_str(timeStr2, sizeof(timeStr2), 0, 2);
+
+	if (isNamespaceHash) // Namespace hash
+	{
+		ens = b2i(4, ecm + ecmLen - 4); // Namespace will be the last 4 bytes of the ecm
+		degrees = (ens >> 16) & 0x0FFF; // Remove not-a-pid flag
+
+		if (degrees > 1800)
+		{
+			degrees = 3600 - degrees;
+			compass = 'W';
+		}
+		else
+		{
+			compass = 'E';
+		}
+
+		if (0 == (ens & 0xFFFF)) // Stripped namespace hash
+		{
+			srvid = b2i(2, ecm + 3);
+			tsid = b2i(2, ecm + ecmLen - 8);
+			onid = b2i(2, ecm + ecmLen - 6);
+			// Printing degree sign "\u00B0" requires c99 standard
+			snprintf(buf, len, "F %08X %s XXXXXXXXXXXXXXXX ; %5.1f%c  %04X:%04X:%04X added: %s",
+								hash, timeStr1, degrees / 10.0, compass, onid, tsid, srvid, timeStr2);
+		}
+		else // Full namespace hash
+		{
+			srvid = b2i(2, ecm + 3);
+			frequency = ens & 0x7FFF; // Remove polarity bit
+			polarisation = ens & 0x8000 ? 'V' : 'H';
+			// Printing degree sign "\u00B0" requires c99 standard
+			snprintf(buf, len, "F %08X %s XXXXXXXXXXXXXXXX ; %5.1f%c %5d%c sid:%04X added: %s",
+								hash, timeStr1, degrees / 10.0, compass, frequency, polarisation, srvid, timeStr2);
+		}
+	}
+	else // Universal hash
+	{
+		srvid = b2i(2, ecm + 3);
+		pmtpid = b2i(2, ecm + 5);
+		pidcount = (ecmLen - 15) / 2; // video + audio pids count
+		snprintf(buf, len, "F %08X %s XXXXXXXXXXXXXXXX ; av:%d pmt:%04X sid:%04X added: %s",
+							hash, timeStr1, pidcount, pmtpid, srvid, timeStr2);
+	}
+}
+
+static int8_t is_common_hash(uint32_t hash)
+{
+	// Check universal hash against a number of commnon universal
+	// hashes in order to warn users about potential key clashes
+
+	switch (hash)
+	{
+		case 0xBAFCD9FD: // 0001 0020 0200 1010 1020 (most common hash)
+			return 1;
+		case 0xA6A4FBD4: // 0001 0800 0200 1010 1020
+			return 1;
+		case 0xEFAB7A4D: // 0001 0800 1010 1020 0200
+			return 1;
+		case 0x83FA15D1: // 0001 0020 0134 0100 0101
+			return 1;
+		case 0x58934C38: // 0001 0800 1010 1020 1030 0200
+			return 1;
+		case 0x2C3CEC17: // 0001 0020 0134 0100
+			return 1;
+		case 0x73DF7F7E: // 0001 0020 0200 1010 1020 1030
+			return 1;
+		case 0xAFA85BC8: // 0001 0020 0021 0022 0023
+			return 1;
+		case 0x8C51F31D: // 0001 0800 0200 1010 1020 1030 1040
+			return 1;
+		case 0xE2F9BD29: // 0001 0800 0200 1010 1020 1030
+			return 1;
+		case 0xB9EBE0FF: // 0001 0100 0200 1010 1020 (less common hash)
+			return 1;
+		default:
+			return 0;
+	}
+}
+
+static int8_t is_valid_namespace(uint32_t namespace)
+{
+	// Note to developers:
+	// If we ever have a satellite at 0.0E, edit to allow stripped namespace
+	// '0xA0000000' with an additional test on tsid and onid being != 0
+
+	uint16_t orbital, frequency;
+
+	orbital = (namespace >> 16) & 0x0FFF;
+	frequency = namespace & 0x7FFF;
+
+	if ((namespace & 0xF0000000) != 0xA0000000) return 0;   // Value isn't flagged as namespace
+	if ((namespace & 0x0FFFFFFF) == 0x00000000) return 0;   // Empty namespace
+	if (orbital > 3599) return 0;                           // Allow only DVB-S
+	if (frequency == 0) return 1;                           // Stripped namespace
+	if (frequency >= 3400 && frequency <= 4200) return 1;   // Super extended C band
+	if (frequency >= 10700 && frequency <= 12750) return 1; // Ku band Europe
+
+	return 0;
+}
+
+static int8_t get_sw(uint32_t provider, uint8_t *sw, uint8_t sw_length, int8_t dateCoded, int8_t printMsg)
+{
+	// If date-coded keys are enabled in the webif, this function evaluates the expiration date
+	// of the found keys. Expired keys are not sent to the calling function. If date-coded keys
+	// are disabled, then every key is sent without any evaluation. It takes the "provider" as
+	// input and outputs the "sw". Returns 0 (key not found, or expired) or 1 (key found).
+
+	// printMsg: 0 => No message
+	// printMsg: 1 => Print message only if key is found
+	// printMsg: 2 => Always print message, regardless if key is found or not
+
+	char keyExpDate[9] = "00000000";
+
+	if (emu_find_key('F', provider, 0, keyExpDate, sw, sw_length, 0, 0, 0, NULL)) // Key found
+	{
+		if (dateCoded) // Date-coded keys are enabled, evaluate expiration date
+		{
+			char currentDate[9];
+			date_to_str(currentDate, sizeof(currentDate), 0, 3);
+
+			if (strncmp("00000000", keyExpDate, 9) == 0 || strncmp(currentDate, keyExpDate, 9) < 0) // Evergreen or not expired
+			{
+				if (printMsg == 1 || printMsg == 2) cs_log("Key found: F %08X %s", provider, keyExpDate);
+				return 1;
+			}
+			else // Key expired
+			{
+				sw = NULL; // Make sure we don't send any expired key
+				if (printMsg == 2) cs_log("Key expired: F %08X %s", provider, keyExpDate);
+				return 0;
+			}
+		}
+		else // Date-coded keys are disabled, don't evaluate expiration date
+		{
+			if (printMsg == 1 || printMsg == 2) cs_log("Key found: F %08X %s", provider, keyExpDate);
+			return 1;
+		}
+	}
+	else // Key not found
+	{
+		if (printMsg == 2) cs_log("Key not found: F %08X", provider);
+		return 0;
+	}
+}
+
+static int8_t biss_mode1_ecm(struct s_reader *rdr, const uint8_t *ecm, uint16_t caid, uint16_t ecm_pid, uint8_t *dw, EXTENDED_CW *cw_ex)
+{
+	// Oscam's fake ecm consists of [sid] [pmtpid] [pid1] [pid2] ... [pidx] [tsid] [onid] [ens]
+	// On enigma boxes tsid, onid and namespace should be non zero, while on non-enigma
+	// boxes they are usually all zero. The top 4 bits of the namespace are flagged with 0xA.
+
+	// The emulator creates a unique channel hash using srvid and enigma namespace or
+	// srvid, tsid, onid and namespace (in case of namespace without frequency) and
+	// another weaker (not unique) hash based on every pid of the channel. This universal
+	// hash should be available on all types of stbs (enigma and non-enigma).
+
+	// Key searches are made from highest priority (tightest test first) to lowest priority
+	// (loosest test last):
+	// 1. Namespace hash (only on enigma boxes)
+	// 2. Universal hash (all box types with emu r752+)
+	// 3. Valid tsid, onid combination
+	// 4. Reverse order pid (audio, video, pmt)
+	// 5. Legacy srvid, ecm pid combination
+	// 6. Default "All Feeds" key
+
+	// If enabled in the webif, a date based key search is performed. If the expiration
+	// date has passed, the key is not sent back from get_sw(). This option is used only
+	// in the namespace hash, universal hash and the "All Feeds" search methods.
+
+	uint32_t i, ens = 0, hash = 0;
+	uint16_t srvid, tsid = 0, onid = 0, pid, ecm_len = SCT_LEN(ecm);
+	uint8_t *sw, sw_length, ecm_copy[ecm_len];
+	char tmp_buffer1[33], tmp_buffer2[90] = "0", tmp_buffer3[90] = "0";
+
+	if (caid == 0x2602 && cw_ex != NULL) // BISS2
+	{
+		cw_ex->mode = CW_MODE_ONE_CW;
+		cw_ex->algo = CW_ALGO_AES128;
+		cw_ex->algo_mode = CW_ALGO_MODE_CBC;
+		memcpy(cw_ex->data, dvb_cissa_iv, 16);
+
+		sw = cw_ex->session_word;
+		sw_length = 16;
+	}
+	else // BISS1
+	{
+		sw = dw;
+		sw_length = 8;
+	}
+
+	srvid = b2i(2, ecm + 3);
+
+	if (ecm_len >= 17) // Likely an r752+ extended ecm
+	{
+		tsid = b2i(2, ecm + ecm_len - 8);
+		onid = b2i(2, ecm + ecm_len - 6);
+		ens = b2i(4, ecm + ecm_len - 4);
+	}
+
+	// 1. Namespace hash (enigma only)
+	if (is_valid_namespace(ens))
+	{
+		unify_orbitals(&ens);
+		memcpy(ecm_copy, ecm, ecm_len);
+		i2b_buf(4, ens, ecm_copy + ecm_len - 4);
+
+		for (i = 0; i < 5; i++) // Find key matching hash made with frequency modified to: f+0, then f-1, f+1, f-2, lastly f+2
+		{
+			ecm_copy[ecm_len - 1] = (i & 1) ? ecm_copy[ecm_len - 1] - i : ecm_copy[ecm_len - 1] + i; // frequency +/- 1, 2 MHz
+
+			if (0 != (ens & 0xFFFF)) // Full namespace - Calculate hash with srvid and namespace only
+			{
+				i2b_buf(2, srvid, ecm_copy + ecm_len - 6); // Put [srvid] right before [ens]
+				hash = crc32(caid, ecm_copy + ecm_len - 6, 6);
+			}
+			else // Namespace without frequency - Calculate hash with srvid, tsid, onid and namespace
+			{
+				i2b_buf(2, srvid, ecm_copy + ecm_len - 10); // Put [srvid] right before [tsid] [onid] [ens] sequence
+				hash = crc32(caid, ecm_copy + ecm_len - 10, 10);
+			}
+
+			if (get_sw(hash, sw, sw_length, rdr->emu_datecodedenabled, i == 0 ? 2 : 1)) // Do not print "key not found" for frequency off by 1, 2
+			{
+				memcpy(sw + sw_length, sw, sw_length);
+				return EMU_OK;
+			}
+
+			if (i == 0) // No key found matching our hash: create example SoftCam.Key BISS line for the live log
+			{
+				annotate(tmp_buffer2, sizeof(tmp_buffer2), ecm_copy, ecm_len, hash, 1, rdr->emu_datecodedenabled);
+			}
+
+			if (0 == (ens & 0xFFFF)) // Namespace without frequency - Do not iterate
+			{
+				break;
+			}
+		}
+	}
+
+	// 2. Universal hash (in r752+ style ecms that contain pmt pid)
+	if ((ens & 0xF0000000) == 0xA0000000)
+	{
+		hash = crc32(caid, ecm + 3, ecm_len - 3 - 8); // Do not include [tsid] [onid] [ens] in the hash
+
+		if (get_sw(hash, sw, sw_length, rdr->emu_datecodedenabled, 2))
+		{
+			memcpy(sw + sw_length, sw, sw_length);
+			return EMU_OK;
+		}
+
+		// No key found matching our hash: create example SoftCam.Key BISS line for the live log
+		annotate(tmp_buffer3, sizeof(tmp_buffer3), ecm_copy, ecm_len, hash, 0, rdr->emu_datecodedenabled);
+	}
+
+	// 3. Valid [tsid] [onid] combination (per enigma2)
+	if (onid != 0 && (onid != 1 || tsid >= 2) && onid < 0xFF00)
+	{
+		if (get_sw(tsid << 16 | onid, sw, sw_length, 0, 2))
+		{
+			memcpy(sw + sw_length, sw, sw_length);
+			return EMU_OK;
+		}
+	}
+
+	// 4. Reverse order pid search
+	// (better identifies channels with variable counts of audio pids)
+	// Strip [tsid] [onid] [ens] on r752+ ecms to be compatible with older versions)
+	if ((ens & 0xF0000000) == 0xA0000000)
+	{
+		ecm_len -= 8;
+	}
+
+	for (i = ecm_len - 2; i >= 5; i -= 2)
+	{
+		pid = b2i(2, ecm + i);
+
+		if (get_sw((srvid << 16) | pid, sw, sw_length, 0, 2))
+		{
+			memcpy(sw + sw_length, sw, sw_length);
+			return EMU_OK;
+		}
+	}
+
+	// 5. Legacy [srvid] [ecm pid] combination
+	if (get_sw((srvid << 16) | ecm_pid, sw, sw_length, 0, 2))
+	{
+		memcpy(sw + sw_length, sw, sw_length);
+		return EMU_OK;
+	}
+
+	// 6. Default BISS key for events with many feeds sharing the same session word
+	// (limited to local ecms, network ecms with ecm pid equal to zero are blocked)
+	if (ecm_pid != 0 && get_sw(0xA11FEED5, sw, sw_length, rdr->emu_datecodedenabled, 2))
+	{
+		memcpy(sw + sw_length, sw, sw_length);
+		cs_hexdump(0, sw, sw_length, tmp_buffer1, sizeof(tmp_buffer1));
+		cs_log("No specific match found. Using 'All Feeds' key: %s", tmp_buffer1);
+		return EMU_OK;
+	}
+
+	// Print example key lines for available hash search methods, if no key is found
+	if (strncmp(tmp_buffer2, "0", 2)) cs_log("Example key based on namespace hash: %s", tmp_buffer2);
+	if (strncmp(tmp_buffer3, "0", 2)) cs_log("Example key based on universal hash: %s", tmp_buffer3);
+
+	// Check if universal hash is common and warn user
+	if (is_common_hash(hash)) cs_log("Feed has commonly used pids, universal hash clashes in SoftCam.Key are likely!");
+
+	return EMU_KEY_NOT_FOUND;
+}
+
+static inline int8_t get_ecm_key(uint16_t onid, uint16_t esid, uint8_t parity, uint8_t *key)
+{
+	return emu_find_key('G', onid << 16 | esid, 0, parity == 0 ? "00" : "01", key, 16, 1, 0, 0, NULL);
+}
+
+static int8_t biss2_mode_ca_ecm(const uint8_t *ecm, EXTENDED_CW *cw_ex)
+{
+	uint8_t ecm_cipher_type, session_key_parity;
+	uint8_t session_key[16], iv[16];
+	uint16_t entitlement_session_id, original_network_id, descriptor_length;
+	uint16_t position, ecm_length = SCT_LEN(ecm);
+	uint32_t payload_checksum, calculated_checksum;
+	char tmp_buffer[64];
+	struct aes_keys aes;
+
+	// Calculate crc32 checksum and compare against the checksum bytes of the ECM
+	payload_checksum = b2i(4, ecm + ecm_length - 4);
+	calculated_checksum = ccitt32_crc((uint8_t *)ecm, ecm_length - 4);
+
+	if (payload_checksum != calculated_checksum)
+	{
+		cs_log_dbg(D_TRACE, "ECM checksum mismatch (payload: %08X vs calculated: %08X",
+					payload_checksum, calculated_checksum);
+		return EMU_CHECKSUM_ERROR;
+	}
+
+	// Unique identifiers of the session key
+	entitlement_session_id = b2i(2, ecm + 3);
+	original_network_id = b2i(2, ecm + 8);
+
+	ecm_cipher_type = ecm[10] >> 5;
+	if (ecm_cipher_type != 0) // Session words shall be encrypted with AES_128_CBC
+	{
+		cs_log("ECM cipher type %d not supported", ecm_cipher_type);
+		return EMU_NOT_SUPPORTED;
+	}
+
+	descriptor_length = b2i(2, ecm + 10) & 0x0FFF;
+	position = 12 + descriptor_length;
+
+	session_key_parity = ecm[position] >> 7; // Parity can be "00" or "01"
+	position++;
+
+	if (!get_ecm_key(original_network_id, entitlement_session_id, session_key_parity, session_key))
+	{
+		return EMU_KEY_NOT_FOUND;
+	}
+
+	memcpy(iv, ecm + position, 16);                            // "AES_128_CBC_enc_session_word_iv"
+	memcpy(cw_ex->session_word, ecm + position + 16, 16);      // "AES_128_CBC_enc_session_word_0"
+	memcpy(cw_ex->session_word + 16, ecm + position + 32, 16); // "AES_128_CBC_enc_session_word_1"
+
+	// Delete these cs_log calls when everything is confirmed to work correctly
+	cs_hexdump(3, iv, 16, tmp_buffer, sizeof(tmp_buffer));
+	cs_log_dbg(D_TRACE, "session_word_iv: %s", tmp_buffer);
+
+	cs_hexdump(3, cw_ex->session_word, 16, tmp_buffer, sizeof(tmp_buffer));
+	cs_log_dbg(D_TRACE, "encrypted session_word_0: %s", tmp_buffer);
+
+	cs_hexdump(3, cw_ex->session_word + 16, 16, tmp_buffer, sizeof(tmp_buffer));
+	cs_log_dbg(D_TRACE, "encrypted session_word_1: %s", tmp_buffer);
+
+	// Decrypt session words
+	aes_set_key(&aes, (char *)session_key);
+	aes_cbc_decrypt(&aes, cw_ex->session_word, 16, iv);
+	memcpy(iv, ecm + position, 16); // Set iv again to the correct one
+	aes_cbc_decrypt(&aes, cw_ex->session_word + 16, 16, iv);
+
+	// Delete these cs_log calls when everything is confirmed to work correctly
+	cs_hexdump(3, cw_ex->session_word, 16, tmp_buffer, sizeof(tmp_buffer));
+	cs_log_dbg(D_TRACE, "decrypted session_word_0: %s", tmp_buffer);
+
+	cs_hexdump(3, cw_ex->session_word + 16, 16, tmp_buffer, sizeof(tmp_buffer));
+	cs_log_dbg(D_TRACE, "decrypted session_word_1: %s", tmp_buffer);
+
+	cw_ex->mode = CW_MODE_ONE_CW;
+	cw_ex->algo = CW_ALGO_AES128;
+	cw_ex->algo_mode = CW_ALGO_MODE_CBC;
+	memcpy(cw_ex->data, dvb_cissa_iv, 16);
+
+	return EMU_OK;
+}
+
+int8_t biss_ecm(struct s_reader *rdr, const uint8_t *ecm, uint16_t caid, uint16_t ecm_pid, uint8_t *dw, EXTENDED_CW *cw_ex)
+{
+	switch (caid)
+	{
+		case 0x2600:
+			return biss_mode1_ecm(rdr, ecm, caid, ecm_pid, dw, NULL);
+
+		case 0x2602:
+			return biss_mode1_ecm(rdr, ecm, caid, ecm_pid, NULL, cw_ex);
+
+		case 0x2610:
+			return biss2_mode_ca_ecm(ecm, cw_ex);
+
+		default:
+			cs_log("Unknown Biss caid %04X - Please report!", caid);
+			return EMU_NOT_SUPPORTED;
+	}
+}
+
+static uint16_t parse_session_data_descriptor(const uint8_t *data, uint16_t esid, uint16_t onid, uint32_t *keysAdded)
+{
+	uint8_t descriptor_tag = data[0];
+	uint8_t descriptor_length = data[1];
+
+	switch (descriptor_tag)
+	{
+		case 0x81: // session_key_descriptor
+		{
+			uint8_t session_key_type = data[2] >> 1;
+			if (session_key_type == 0) // AES-128
+			{
+				uint8_t session_key_parity = data[2] & 0x01;
+				uint8_t session_key_data[16];
+				memcpy(session_key_data, data + 3, 16); // This is the ECM key
+
+				SAFE_MUTEX_LOCK(&emu_key_data_mutex);
+				if (emu_update_key('G', onid << 16 | esid, session_key_parity ? "01" : "00", session_key_data, 16, 1, NULL))
+				{
+					(*keysAdded)++;
+					char tmp[33];
+					cs_hexdump(0, session_key_data, 16, tmp, sizeof(tmp));
+					cs_log("Key found in EMM: G %08X %02d %s", onid << 16 | esid, session_key_parity, tmp);
+				}
+				SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
+			}
+			break;
+		}
+
+		case 0x82: // entitlement_flags_descriptor
+			break;
+
+		default:
+			break;
+	}
+
+	return 2 + descriptor_length;
+}
+
+static int8_t parse_session_data(const uint8_t *data, RSA *key, uint16_t esid, uint16_t onid, uint32_t *keysAdded)
+{
+	// session_data is encrypted with RSA 2048 bit OAEP
+	// Maximum size of decrypted session_data is less than (256-41) bytes
+	uint8_t session_data[214];
+
+	if (RSA_private_decrypt(256, data, session_data, key, RSA_PKCS1_OAEP_PADDING) > 0)
+	{
+		uint16_t pos = 0;
+		uint16_t descriptor_length = b2i(2, session_data) & 0x0FFF;
+
+		while (pos < descriptor_length)
+		{
+			pos += parse_session_data_descriptor(session_data + 2 + pos, esid, onid, keysAdded);
+		}
+
+		return EMU_OK;
+	}
+
+	return EMU_NOT_SUPPORTED; // Decryption failed for whatever reason
+}
+
+static int8_t get_rsa_key(struct s_reader *rdr, const uint8_t *ekid, RSA **key)
+{
+	LL_ITER itr;
+	biss2_rsa_key_t *data;
+
+	itr = ll_iter_create(rdr->ll_biss2_rsa_keys);
+	while ((data = ll_iter_next(&itr)))
+	{
+		if (data->ekid == ekid)
+		{
+			*key = data->key;
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+int8_t biss_emm(struct s_reader *rdr, const uint8_t *emm, uint32_t *keysAdded)
+{
+	uint8_t emm_cipher_type, entitlement_priv_data_loop, entitlement_key_id[8];
+	uint16_t entitlement_session_id, original_network_id, descriptor_length;
+	uint16_t pos, emm_length = SCT_LEN(emm);
+	uint32_t payload_checksum, calculated_checksum;
+	int8_t result = EMU_NOT_SUPPORTED;
+	char tmp[17];
+	RSA *key;
+
+	// Calculate crc32 checksum and compare against the checksum bytes of the EMM
+	payload_checksum = b2i(4, emm + emm_length - 4);
+	calculated_checksum = ccitt32_crc((uint8_t *)emm, emm_length - 4);
+
+	if (payload_checksum != calculated_checksum)
+	{
+		cs_log_dbg(D_TRACE, "EMM checksum mismatch (payload: %08X vs calculated: %08X",
+					payload_checksum, calculated_checksum);
+		return EMU_CHECKSUM_ERROR;
+	}
+
+	// Identifiers of the session key carried in the EMM
+	// We just pass them to the "parse_session_data()" function
+	entitlement_session_id = b2i(2, emm + 3);
+	original_network_id = b2i(2, emm + 8);
+	cs_log_dbg(D_TRACE, "onid: %04X, esid: %04X", original_network_id, entitlement_session_id);
+
+	emm_cipher_type = emm[11] >> 5; // top 3 bits;
+	entitlement_priv_data_loop = (emm[11] >> 4) & 0x01; // 4th bit
+
+	if (emm_cipher_type != 0) // EMM payload is not encrypted with RSA_2048_OAEP
+	{
+		cs_log_dbg(D_TRACE, "EMM cipher type %d not supported", emm_cipher_type);
+		return EMU_NOT_SUPPORTED;
+	}
+
+	descriptor_length = b2i(2, emm + 12) & 0x0FFF;
+	pos = 14 + descriptor_length;
+
+	while (pos < emm_length - 4)
+	{
+		// Unique identifier of the public rsa key used for "session_data" encryption
+		memcpy(entitlement_key_id, emm + pos, 8);
+		pos += 8;
+
+		if (get_rsa_key(rdr, entitlement_key_id, &key)) // Key found
+		{
+			cs_hexdump(0, entitlement_key_id, 8, tmp, sizeof(tmp));
+			cs_log_dbg(D_TRACE, "RSA key found (ekid: %s)", tmp);
+
+			// Parse "encrypted_session_data"
+			result = parse_session_data(emm + pos, key, entitlement_session_id, original_network_id, keysAdded);
+			if (result == EMU_OK)
+			{
+				break; // No need to decrypt again with another key
+			}
+		}
+		else // Multiple ekid's can be present in the EMM - Do not exit just yet
+		{
+			cs_hexdump(0, entitlement_key_id, 8, tmp, sizeof(tmp));
+			cs_log_dbg(D_TRACE, "RSA key not found (ekid: %s)", tmp);
+
+			result = EMU_KEY_NOT_FOUND;
+		}
+
+		pos += 256; // 2048 bits
+
+		if (entitlement_priv_data_loop) // Skip any remaining bytes
+		{
+			pos += 2 + (b2i(2, emm + pos) & 0x0FFF);
+		}
+	}
+
+	return result;
+}
+
+static int8_t rsa_key_exists(struct s_reader *rdr, const biss2_rsa_key_t *item)
+{
+	LL_ITER itr;
+	biss2_rsa_key_t *data;
+
+	itr = ll_iter_create(rdr->ll_biss2_rsa_keys);
+	while ((data = ll_iter_next(&itr)))
+	{
+		if (data->ekid == item->ekid)
+		{
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+uint16_t biss_read_pem(struct s_reader *rdr, uint8_t max_keys)
+{
+	FILE *fp_pri = NULL;
+	//FILE *fp_pub = NULL;
+
+	char tmp[256];
+	uint8_t hash[32], *der = NULL;
+	uint16_t i, length, count = 0;;
+	biss2_rsa_key_t *new_item;
+
+	if (!rdr->ll_biss2_rsa_keys)
+	{
+		rdr->ll_biss2_rsa_keys = ll_create("ll_biss2_rsa_keys");
+	}
+
+	for (i = 0; i < max_keys; i++)
+	{
+		if (!cs_malloc(&new_item, sizeof(biss2_rsa_key_t)))
+		{
+			break; // No memory available (?) - Exit
+		}
+
+		snprintf(tmp, sizeof(tmp), "%sbiss2_private_%02d.pem", emu_keyfile_path, i);
+		if ((fp_pri = fopen(tmp, "r")) == NULL)
+		{
+			continue; // File does not exist
+		}
+
+		cs_log("Reading RSA key from: biss2_private_%02d.pem", i);
+
+		// Read RSA private key
+		if ((new_item->key = PEM_read_RSAPrivateKey(fp_pri, NULL, NULL, NULL)) == NULL)
+		{
+			cs_log("Error reading RSA private key");
+			continue;
+		}
+
+		fclose(fp_pri);
+
+		// Write public key in PEM formatted file
+		/*snprintf(tmp, sizeof(tmp), "%sbiss2_public_%02d.pem", emu_keyfile_path, i);
+		if ((fp_pub = fopen(tmp, "w")) != NULL)
+		{
+			PEM_write_RSA_PUBKEY(fp_pub, item->key);
+			fclose(fp_pub);
+		}*/
+
+		// Write public key in DER formatted file
+		/*snprintf(tmp, sizeof(tmp), "%sbiss2_public_%02d.der", emu_keyfile_path, i);
+		if ((fp_pub = fopen(tmp, "wb")) != NULL)
+		{
+			i2d_RSA_PUBKEY_fp(fp_pub, item->key);
+			fclose(fp_pub);
+		}*/
+
+		// Encode RSA public key into DER format
+		if ((length = i2d_RSA_PUBKEY(new_item->key, &der)) <= 0)
+		{
+			cs_log("Error encoding to DER format");
+			NULLFREE(der);
+			continue;
+		}
+
+		// Create SHA256 digest
+		EVP_MD_CTX *mdctx;
+		if ((mdctx = EVP_MD_CTX_create()) == NULL)
+		{
+			NULLFREE(der);
+			continue;
+		}
+
+		EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL);
+		EVP_DigestUpdate(mdctx, der, length);
+		EVP_DigestFinal_ex(mdctx, hash, NULL);
+		EVP_MD_CTX_destroy(mdctx);
+
+		NULLFREE(der);
+		memcpy(new_item->ekid, hash, 8);
+
+		// Add new RSA key, if not already present
+		if (!rsa_key_exists(rdr, new_item))
+		{
+			ll_append(rdr->ll_biss2_rsa_keys, new_item);
+			count++;
+		}
+	}
+
+	return count;
+}
+
+#endif // WITH_EMU
diff --git a/module-emulator-biss.h b/module-emulator-biss.h
new file mode 100644
index 00000000..0f5900cc
--- /dev/null
+++ b/module-emulator-biss.h
@@ -0,0 +1,22 @@
+#ifndef MODULE_EMULATOR_BISS_H
+#define MODULE_EMULATOR_BISS_H
+
+#ifdef WITH_EMU
+
+#include <openssl/rsa.h>
+
+#define BISS2_MAX_RSA_KEYS 16
+
+typedef struct biss2_rsa_key
+{
+	uint8_t ekid[8];
+	RSA *key;
+} biss2_rsa_key_t;
+
+int8_t biss_ecm(struct s_reader *rdr, const uint8_t *ecm, uint16_t caid, uint16_t ecm_pid, uint8_t *dw, EXTENDED_CW *cw_ex);
+int8_t biss_emm(struct s_reader *rdr, const uint8_t *emm, uint32_t *keysAdded);
+uint16_t biss_read_pem(struct s_reader *rdr, uint8_t max_keys);
+
+#endif // WITH_EMU
+
+#endif // MODULE_EMULATOR_BISS_H
diff --git a/module-emulator-cryptoworks.c b/module-emulator-cryptoworks.c
new file mode 100644
index 00000000..df6509ac
--- /dev/null
+++ b/module-emulator-cryptoworks.c
@@ -0,0 +1,688 @@
+#define MODULE_LOG_PREFIX "emu"
+
+#include "globals.h"
+
+#ifdef WITH_EMU
+
+#include "cscrypt/des.h"
+#include "module-emulator-osemu.h"
+
+// Cryptoworks EMU
+
+static int8_t get_key(uint8_t *buf,uint32_t ident, uint8_t keyIndex, uint32_t keyLength, uint8_t isCriticalKey)
+{
+	char keyName[EMU_MAX_CHAR_KEYNAME];
+	uint32_t tmp;
+
+	if ((ident >> 4) == 0xD02A)
+	{
+		keyIndex &= 0xFE; // map to even number key indexes
+	}
+
+	if ((ident >> 4) == 0xD00C)
+	{
+		ident = 0x0D00C0; // map provider C? to C0
+	}
+	else if (keyIndex == 6 && ((ident >> 8) == 0x0D05))
+	{
+		ident = 0x0D0504; // always use provider 04 system key
+	}
+
+	tmp = keyIndex;
+	snprintf(keyName, EMU_MAX_CHAR_KEYNAME, "%.2X", tmp);
+
+	if (emu_find_key('W', ident, 0, keyName, buf, keyLength, isCriticalKey, 0, 0, NULL))
+	{
+		return 1;
+	}
+
+	return 0;
+}
+
+static const uint8_t cw_sbox1[64] =
+{
+	0xD8, 0xD7, 0x83, 0x3D, 0x1C, 0x8A, 0xF0, 0xCF, 0x72, 0x4C, 0x4D, 0xF2, 0xED, 0x33, 0x16, 0xE0,
+	0x8F, 0x28, 0x7C, 0x82, 0x62, 0x37, 0xAF, 0x59, 0xB7, 0xE0, 0x00, 0x3F, 0x09, 0x4D, 0xF3, 0x94,
+	0x16, 0xA5, 0x58, 0x83, 0xF2, 0x4F, 0x67, 0x30, 0x49, 0x72, 0xBF, 0xCD, 0xBE, 0x98, 0x81, 0x7F,
+	0xA5, 0xDA, 0xA7, 0x7F, 0x89, 0xC8, 0x78, 0xA7, 0x8C, 0x05, 0x72, 0x84, 0x52, 0x72, 0x4D, 0x38
+};
+
+static const uint8_t cw_sbox2[64] =
+{
+	0xD8, 0x35, 0x06, 0xAB, 0xEC, 0x40, 0x79, 0x34, 0x17, 0xFE, 0xEA, 0x47, 0xA3, 0x8F, 0xD5, 0x48,
+	0x0A, 0xBC, 0xD5, 0x40, 0x23, 0xD7, 0x9F, 0xBB, 0x7C, 0x81, 0xA1, 0x7A, 0x14, 0x69, 0x6A, 0x96,
+	0x47, 0xDA, 0x7B, 0xE8, 0xA1, 0xBF, 0x98, 0x46, 0xB8, 0x41, 0x45, 0x9E, 0x5E, 0x20, 0xB2, 0x35,
+	0xE4, 0x2F, 0x9A, 0xB5, 0xDE, 0x01, 0x65, 0xF8, 0x0F, 0xB2, 0xD2, 0x45, 0x21, 0x4E, 0x2D, 0xDB
+};
+
+static const uint8_t cw_sbox3[64] =
+{
+	0xDB, 0x59, 0xF4, 0xEA, 0x95, 0x8E, 0x25, 0xD5, 0x26, 0xF2, 0xDA, 0x1A, 0x4B, 0xA8, 0x08, 0x25,
+	0x46, 0x16, 0x6B, 0xBF, 0xAB, 0xE0, 0xD4, 0x1B, 0x89, 0x05, 0x34, 0xE5, 0x74, 0x7B, 0xBB, 0x44,
+	0xA9, 0xC6, 0x18, 0xBD, 0xE6, 0x01, 0x69, 0x5A, 0x99, 0xE0, 0x87, 0x61, 0x56, 0x35, 0x76, 0x8E,
+	0xF7, 0xE8, 0x84, 0x13, 0x04, 0x7B, 0x9B, 0xA6, 0x7A, 0x1F, 0x6B, 0x5C, 0xA9, 0x86, 0x54, 0xF9
+};
+
+static const uint8_t cw_sbox4[64] =
+{
+	0xBC, 0xC1, 0x41, 0xFE, 0x42, 0xFB, 0x3F, 0x10, 0xB5, 0x1C, 0xA6, 0xC9, 0xCF, 0x26, 0xD1, 0x3F,
+	0x02, 0x3D, 0x19, 0x20, 0xC1, 0xA8, 0xBC, 0xCF, 0x7E, 0x92, 0x4B, 0x67, 0xBC, 0x47, 0x62, 0xD0,
+	0x60, 0x9A, 0x9E, 0x45, 0x79, 0x21, 0x89, 0xA9, 0xC3, 0x64, 0x74, 0x9A, 0xBC, 0xDB, 0x43, 0x66,
+	0xDF, 0xE3, 0x21, 0xBE, 0x1E, 0x16, 0x73, 0x5D, 0xA2, 0xCD, 0x8C, 0x30, 0x67, 0x34, 0x9C, 0xCB
+};
+
+static const uint8_t AND_bit1[8] = { 0x00, 0x40, 0x04, 0x80, 0x21, 0x10, 0x02, 0x08 };
+static const uint8_t AND_bit2[8] = { 0x80, 0x08, 0x01, 0x40, 0x04, 0x20, 0x10, 0x02 };
+static const uint8_t AND_bit3[8] = { 0x82, 0x40, 0x01, 0x10, 0x00, 0x20, 0x04, 0x08 };
+static const uint8_t AND_bit4[8] = { 0x02, 0x10, 0x04, 0x40, 0x80, 0x08, 0x01, 0x20 };
+
+static void swap_key(uint8_t *key)
+{
+	uint8_t k[8];
+	memcpy(k, key, 8);
+	memcpy(key, key + 8, 8);
+	memcpy(key + 8, k, 8);
+}
+
+static void swap_data(uint8_t *k)
+{
+	uint8_t d[4];
+	memcpy(d, k + 4, 4);
+	memcpy(k + 4, k, 4);
+	memcpy(k, d, 4);
+}
+
+static void des_round(uint8_t *d, uint8_t *k)
+{
+	uint8_t aa[44] =
+	{
+		1, 0, 3, 1, 2, 2, 3, 2, 1, 3, 1, 1, 3, 0, 1, 2, 3, 1, 3, 2, 2, 0,
+		7, 6, 5, 4, 7, 6, 5, 7, 6, 5, 6, 7, 5, 7, 5, 7, 6, 6, 7, 5, 4, 4
+	};
+
+	uint8_t bb[44] =
+	{
+		0x80, 0x08, 0x10, 0x02, 0x08, 0x40, 0x01, 0x20, 0x40, 0x80, 0x04,
+		0x10, 0x04, 0x01, 0x01, 0x02, 0x20, 0x20, 0x02, 0x01, 0x80, 0x04,
+		0x02, 0x02, 0x08, 0x02, 0x10, 0x80, 0x01, 0x20, 0x08, 0x80, 0x01,
+		0x08, 0x40, 0x01, 0x02, 0x80, 0x10, 0x40, 0x40, 0x10, 0x08, 0x01
+	};
+
+	uint8_t ff[4] = { 0x02, 0x10, 0x04, 0x04};
+	uint8_t l[24] = { 0, 2, 4, 6, 7, 5, 3, 1, 4, 5, 6, 7, 7, 6, 5, 4, 7, 4, 5, 6, 4, 7, 6, 5 };
+
+	uint8_t des_td[8], i, o, n, c = 1, m = 0, r = 0;
+	uint8_t *a = aa, *b = bb, *f = ff, *p1 = l, *p2 = l + 8, *p3 = l + 16;
+
+	for (m = 0; m < 2; m++)
+	{
+		for (i = 0; i < 4; i++)
+		{
+			des_td[*p1++] = (m) ? ((d[*p2++] * 2) & 0x3F) | ((d[*p3++] & 0x80) ? 0x01 : 0x00) :
+									(d[*p2++] / 2) | ((d[*p3++] & 0x01) ? 0x80 : 0x00);
+		}
+	}
+
+	for (i = 0; i < 8; i++)
+	{
+		c = (c) ? 0 : 1;
+		r = (c) ? 6 : 7;
+		n = (i) ? i - 1 : 1;
+		o = (c) ? ((k[n] & *f++) ? 1 : 0) : des_td[n];
+
+		for (m = 1; m < r; m++)
+		{
+			o = (c) ? (o * 2) | ((k[*a++] & *b++) ? 0x01 : 0x00) : (o / 2) | ((k[*a++] & *b++) ? 0x80 : 0x00);
+		}
+
+		n = (i) ? n + 1 : 0;
+		des_td[n] = (c) ? des_td[n] ^ o : (o ^ des_td[n]) / 4;
+	}
+
+	for (i = 0; i < 8; i++)
+	{
+		d[0] ^= (AND_bit1[i] & cw_sbox1[des_td[i]]);
+		d[1] ^= (AND_bit2[i] & cw_sbox2[des_td[i]]);
+		d[2] ^= (AND_bit3[i] & cw_sbox3[des_td[i]]);
+		d[3] ^= (AND_bit4[i] & cw_sbox4[des_td[i]]);
+	}
+
+	swap_data(d);
+}
+
+static void cw_48_key(uint8_t *inkey, uint8_t *outkey, uint8_t algotype)
+{
+	uint8_t round_counter, i = 8;
+	uint8_t *key128 = inkey;
+	uint8_t *key48 = inkey + 0x10;
+
+	round_counter = 7 - (algotype & 7);
+
+	memset(outkey, 0, 16);
+	memcpy(outkey, key48, 6);
+
+	for ( ; i > round_counter; i--)
+	{
+		if (i > 1)
+		{
+			outkey[i - 2] = key128[i];
+		}
+	}
+}
+
+static void ls_des_key(uint8_t *key, uint8_t rotate_counter)
+{
+	uint8_t i, n;
+	uint8_t rnd[] = { 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1, 1 };
+	uint16_t k[8];
+
+	n = rnd[rotate_counter];
+
+	for (i = 0; i < 8; i++)
+	{
+		k[i] = key[i];
+	}
+
+	for (i = 1; i < n + 1; i++)
+	{
+		k[7] = (k[7] * 2) | ((k[4] & 0x008) ? 1 : 0);
+		k[6] = (k[6] * 2) | ((k[7] & 0xF00) ? 1 : 0);
+		k[7] &= 0xFF;
+
+		k[5] = (k[5] * 2) | ((k[6] & 0xF00) ? 1 : 0);
+		k[6] &= 0xFF;
+
+		k[4] = ((k[4] * 2) | ((k[5] & 0xF00) ? 1 : 0)) & 0xFF;
+		k[5] &= 0xFF;
+
+		k[3] = (k[3] * 2) | ((k[0] & 0x008) ? 1 : 0);
+		k[2] = (k[2] * 2) | ((k[3] & 0xF00) ? 1 : 0);
+		k[3] &= 0xFF;
+
+		k[1] = (k[1] * 2) | ((k[2] & 0xF00) ? 1 : 0);
+		k[2] &= 0xFF;
+
+		k[0] = ((k[0] * 2) | ((k[1] & 0xF00) ? 1 : 0)) & 0xFF;
+		k[1] &= 0xFF;
+	}
+
+	for (i = 0; i < 8; i++)
+	{
+		key[i] = (uint8_t) k[i];
+	}
+}
+
+static void rs_des_key(uint8_t *k, uint8_t rotate_counter)
+{
+	uint8_t i, c;
+
+	for (i = 1; i < rotate_counter + 1; i++)
+	{
+		c = (k[3] & 0x10) ? 0x80 : 0;
+		k[3] /= 2;
+
+		if (k[2] & 1)
+		{
+			k[3] |= 0x80;
+		}
+
+		k[2] /= 2;
+
+		if (k[1] & 1)
+		{
+			k[2] |= 0x80;
+		}
+
+		k[1] /= 2;
+
+		if (k[0] & 1)
+		{
+			k[1] |= 0x80;
+		}
+
+		k[0] /= 2;
+		k[0] |= c ;
+		c = (k[7] & 0x10) ? 0x80 : 0;
+		k[7] /= 2;
+
+		if (k[6] & 1)
+		{
+			k[7] |= 0x80;
+		}
+
+		k[6] /= 2;
+
+		if (k[5] & 1)
+		{
+			k[6] |= 0x80;
+		}
+
+		k[5] /= 2;
+
+		if (k[4] & 1)
+		{
+			k[5] |= 0x80;
+		}
+
+		k[4] /= 2;
+		k[4] |= c;
+	}
+}
+
+static void rs_des_subkey(uint8_t *k, uint8_t rotate_counter)
+{
+	uint8_t rnd[] = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
+
+	rs_des_key(k, rnd[rotate_counter]);
+}
+
+static void prep_key(uint8_t *key)
+{
+	int32_t round_counter = 6, i, a;
+	uint8_t DES_key[8], j;
+
+	key[7] = 6;
+	memset(DES_key, 0, 8);
+
+	do
+	{
+		a = 7;
+		i = key[7];
+		j = key[round_counter];
+
+		do
+		{
+			DES_key[i] = ( (DES_key[i] * 2) | ((j & 1) ? 1 : 0) ) & 0xFF;
+
+			j /= 2;
+			i--;
+
+			if (i < 0)
+			{
+				i = 6;
+			}
+			a--;
+		}
+		while (a >= 0);
+
+		key[7] = i;
+		round_counter--;
+	}
+	while (round_counter >= 0);
+
+	a = DES_key[4];
+	DES_key[4] = DES_key[6];
+	DES_key[6] = a;
+	DES_key[7] = (DES_key[3] * 16) & 0xFF;
+
+	memcpy(key, DES_key, 8);
+	rs_des_key(key, 4);
+}
+
+static void l2_des(uint8_t *data, uint8_t *key, uint8_t algo)
+{
+	uint8_t i, k0[22], k1[22];
+
+	memcpy(k0, key, 22);
+	memcpy(k1, key, 22);
+
+	cw_48_key(k0, k1, algo);
+	prep_key(k1);
+
+	for (i = 0; i < 2; i++)
+	{
+		ls_des_key(k1, 15);
+		des_round(data, k1);
+	}
+}
+
+static void r2_des(uint8_t *data, uint8_t *key, uint8_t algo)
+{
+	uint8_t i, k0[22], k1[22];
+
+	memcpy(k0, key, 22);
+	memcpy(k1, key, 22);
+
+	cw_48_key(k0, k1, algo);
+	prep_key(k1);
+
+	for (i = 0; i < 2; i++)
+	{
+		ls_des_key(k1, 15);
+	}
+
+	for (i = 0; i < 2; i++)
+	{
+		des_round(data, k1);
+		rs_des_subkey(k1, 1);
+	}
+
+	swap_data(data);
+}
+
+static void cw_des(uint8_t *data, uint8_t *inkey, uint8_t m)
+{
+	uint8_t key[22], i;
+
+	memcpy(key, inkey + 9, 8);
+	prep_key(key);
+
+	for (i = 16; i > 0; i--)
+	{
+		if (m == 1)
+		{
+			ls_des_key(key, (uint8_t) (i - 1));
+		}
+
+		des_round( data ,key);
+
+		if (m == 0)
+		{
+			rs_des_subkey(key, (uint8_t) (i - 1));
+		}
+	}
+}
+
+static void cw_dec_enc(uint8_t *d, uint8_t *k, uint8_t a, uint8_t m)
+{
+	uint8_t n = m & 1;
+
+	l2_des(d, k, a);
+	cw_des(d, k, n);
+	r2_des(d, k, a);
+
+	if (m & 2)
+	{
+		swap_key(k);
+	}
+}
+
+static uint8_t process_nano80(uint8_t *data, uint32_t caid, int32_t provider, uint8_t *opKey,
+								uint8_t nanoLength, uint8_t nano80Algo)
+{
+	int32_t i, j;
+	uint8_t key[16], desKey[16], t[8], dat1[8], dat2[8], k0D00C000[16];
+
+	if (nanoLength < 11)
+	{
+		return 0;
+	}
+
+	if (caid == 0x0D00 && provider != 0xA0 && !get_key(k0D00C000, 0x0D00C0, 0, 16, 1))
+	{
+		return 0;
+	}
+
+	if (nano80Algo > 1)
+	{
+		return 0;
+	}
+
+	memset(t, 0, 8);
+	memcpy(dat1, data, 8);
+
+	if(caid == 0x0D00 && provider != 0xA0)
+	{
+		memcpy(key, k0D00C000, 16);
+	}
+	else
+	{
+		memcpy(key, opKey, 16);
+	}
+
+	des_ecb3_decrypt(data, key);
+	memcpy(desKey, data, 8);
+	memcpy(data, dat1, 8);
+
+	if (caid == 0x0D00 && provider != 0xA0)
+	{
+		memcpy(key, &k0D00C000[8], 8);
+		memcpy(&key[8], k0D00C000, 8);
+	}
+	else
+	{
+		memcpy(key, &opKey[8], 8);
+		memcpy(&key[8], opKey, 8);
+	}
+
+	des_ecb3_decrypt(data, key);
+	memcpy(&desKey[8], data, 8);
+
+	for (i = 8; i + 7 < nanoLength; i += 8)
+	{
+		memcpy(dat1, &data[i], 8);
+		memcpy(dat2, dat1, 8);
+		memcpy(key, desKey, 16);
+		des_ecb3_decrypt(dat1, key);
+
+		for (j = 0; j < 8; j++)
+		{
+			dat1[j] ^= t[j];
+		}
+
+		memcpy(&data[i], dat1, 8);
+		memcpy(t, dat2, 8);
+	}
+
+	return data[10] + 5;
+}
+
+static void cryptoworks_signature(const uint8_t *data, uint32_t length, uint8_t *key, uint8_t *signature)
+{
+	uint32_t i, sigPos;
+	int8_t algo, first;
+
+	algo = data[0] & 7;
+	if (algo == 7)
+	{
+		algo = 6;
+	}
+
+	memset(signature, 0, 8);
+	first = 1;
+	sigPos = 0;
+
+	for (i = 0; i < length; i++)
+	{
+		signature[sigPos] ^= data[i];
+		sigPos++;
+
+		if (sigPos > 7)
+		{
+			if (first)
+			{
+				l2_des(signature, key, algo);
+			}
+
+			cw_des(signature, key, 1);
+
+			sigPos = 0;
+			first = 0;
+		}
+	}
+
+	if (sigPos > 0)
+	{
+		cw_des(signature, key, 1);
+	}
+
+	r2_des(signature, key, algo);
+}
+
+static void decrypt_des(uint8_t *data, uint8_t algo, uint8_t *key)
+{
+	int32_t i;
+	uint8_t k[22], t[8];
+
+	algo &= 7;
+
+	if (algo < 7)
+	{
+		cw_dec_enc(data, key, algo, 0);
+	}
+	else
+	{
+		memcpy(k, key, 22);
+
+		for (i = 0; i < 3; i++)
+		{
+			cw_dec_enc(data, k, algo, i & 1);
+
+			memcpy(t, k, 8);
+			memcpy(k, k + 8, 8);
+			memcpy(k + 8, t, 8);
+		}
+	}
+}
+
+int8_t cryptoworks_ecm(uint32_t caid, uint8_t *ecm, uint8_t *cw)
+{
+	int32_t provider = -1;
+	uint8_t keyIndex = 0, nanoLength, newEcmLength, key[22], signature[8], nano80Algo = 1;
+	uint16_t i, j, ecmLen = SCT_LEN(ecm);
+	uint32_t ident;
+
+	if (ecmLen < 8)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	if (ecm[7] != ecmLen - 8)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	memset(key, 0, 22);
+
+	for (i = 8; i + 1 < ecmLen; i += ecm[i + 1] + 2)
+	{
+		if (ecm[i] == 0x83 && i + 2 < ecmLen)
+		{
+			provider = ecm[i + 2] & 0xFC;
+			keyIndex = ecm[i + 2] & 3;
+			keyIndex = keyIndex ? 1 : 0;
+		}
+		else if (ecm[i] == 0x84 && i + 3 < ecmLen)
+		{
+			//nano80Provider = ecm[i + 2] & 0xFC;
+			//nano80KeyIndex = ecm[i + 2] & 3;
+			//nano80KeyIndex = nano80KeyIndex ? 1 : 0;
+			nano80Algo = ecm[i + 3];
+		}
+	}
+
+	if (provider < 0)
+	{
+		switch (caid)
+		{
+			case 0x0D00:
+				provider = 0xC0;
+				break;
+
+			case 0x0D02:
+				provider = 0xA0;
+				break;
+
+			case 0x0D03:
+				provider = 0x04;
+				break;
+
+			case 0x0D05:
+				provider = 0x04;
+				break;
+
+			default:
+				return EMU_NOT_SUPPORTED;
+		}
+	}
+
+	ident = (caid << 8) | provider;
+
+	if (!get_key(key, ident, keyIndex, 16, 1))
+	{
+		return EMU_KEY_NOT_FOUND;
+	}
+
+	if (!get_key(&key[16], ident, 6, 6, 1))
+	{
+		return EMU_KEY_NOT_FOUND;
+	}
+
+	for (i = 8; i + 1 < ecmLen; i += ecm[i + 1] + 2)
+	{
+		if (ecm[i] == 0x80 && i + 2 + 7 < ecmLen && i + 2 + ecm[i + 1] <= ecmLen &&
+			(provider == 0xA0 || provider == 0xC0 || provider == 0xC4 || provider == 0xC8))
+		{
+			nanoLength = ecm[i + 1];
+			newEcmLength = process_nano80(ecm + i + 2, caid, provider, key, nanoLength, nano80Algo);
+
+			if (newEcmLength == 0 || newEcmLength > ecmLen - (i + 2 + 3))
+			{
+				return EMU_NOT_SUPPORTED;
+			}
+
+			ecm[i + 2 + 3] = 0x81;
+			ecm[i + 2 + 4] = 0x70;
+			ecm[i + 2 + 5] = newEcmLength;
+			ecm[i + 2 + 6] = 0x81;
+			ecm[i + 2 + 7] = 0xFF;
+
+			return cryptoworks_ecm(caid, ecm + i + 2 + 3, cw);
+		}
+	}
+
+	if (ecmLen - 15 < 1)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	cryptoworks_signature(ecm + 5, ecmLen - 15, key, signature);
+
+	for (i = 8; i + 1 < ecmLen; i += ecm[i + 1] + 2)
+	{
+		switch (ecm[i])
+		{
+			case 0xDA:
+			case 0xDB:
+			case 0xDC:
+				if (i + 2 + ecm[i + 1] > ecmLen)
+				{
+					break;
+				}
+				for (j = 0; j + 7 < ecm[i + 1]; j += 8)
+				{
+					decrypt_des(&ecm[i + 2 + j], ecm[5], key);
+				}
+				break;
+
+			case 0xDF:
+				if (i + 2 + 8 > ecmLen)
+				{
+					break;
+				}
+				if (memcmp(&ecm[i + 2], signature, 8))
+				{
+					return EMU_CHECKSUM_ERROR;
+				}
+				break;
+		}
+	}
+
+	for (i = 8; i + 1 < ecmLen; i += ecm[i + 1] + 2)
+	{
+		switch (ecm[i])
+		{
+			case 0xDB:
+				if (i + 2 + ecm[i + 1] <= ecmLen && ecm[i + 1] == 16)
+				{
+					memcpy(cw, &ecm[i + 2], 16);
+					return EMU_OK;
+				}
+				break;
+		}
+	}
+
+	return EMU_CW_NOT_FOUND;
+}
+
+#endif // WITH_EMU
diff --git a/module-emulator-cryptoworks.h b/module-emulator-cryptoworks.h
new file mode 100644
index 00000000..c2424e24
--- /dev/null
+++ b/module-emulator-cryptoworks.h
@@ -0,0 +1,10 @@
+#ifndef MODULE_EMULATOR_CRYPTOWORKS_H
+#define MODULE_EMULATOR_CRYPTOWORKS_H
+
+#ifdef WITH_EMU
+
+int8_t cryptoworks_ecm(uint32_t caid, uint8_t *ecm, uint8_t *cw);
+
+#endif // WITH_EMU
+
+#endif // MODULE_EMULATOR_CRYPTOWORKS_H
diff --git a/module-emulator-director.c b/module-emulator-director.c
new file mode 100644
index 00000000..c8c28b9c
--- /dev/null
+++ b/module-emulator-director.c
@@ -0,0 +1,644 @@
+#define MODULE_LOG_PREFIX "emu"
+
+#include "globals.h"
+
+#ifdef WITH_EMU
+
+#include "cscrypt/des.h"
+#include "module-emulator-osemu.h"
+#include "oscam-aes.h"
+#include "oscam-string.h"
+
+/*************************************************************************************************/
+
+// Shared functions
+
+static uint16_t calculate_checksum(uint8_t *data, uint8_t length)
+{
+	/*
+	 * ECM and EMM checksum calculation
+	 * 1. Combine data in 2 byte groups
+	 * 2. Add them together
+	 * 3. Multiply result by itself (power of 7)
+	 * 4. XOR with fixed value 0x17E3
+	*/
+
+	uint8_t i;
+	uint16_t checksum = 0;
+
+	for (i = 0; i < length; i += 2)
+	{
+		checksum += (data[i] << 8) | data[i + 1];
+	}
+
+	checksum = checksum * checksum * checksum * checksum * checksum * checksum * checksum;
+	checksum ^= 0x17E3;
+
+	return checksum;
+}
+
+static inline int8_t get_key(uint32_t keyIndex, char *keyName, uint8_t *key, uint32_t keyLength)
+{
+	/*
+	 * keyIndex meaning for:
+	 * ecm keys --> entitlementId
+	 * emm keys --> aeskeyIndex
+	 * aes keys --> keyIndex
+	 *
+	 * keyName meaning for:
+	 * ecm keys --> "01"
+	 * emm keys --> "MK" or "MK01"
+	 * aes keys --> "AES"
+	*/
+
+	return emu_find_key('T', keyIndex, 0, keyName, key, keyLength, 1, 0, 0, NULL);
+}
+
+/*************************************************************************************************/
+
+/*
+ * Director ECM emulator
+ * Supported versions: v4, v5, v6 (not working correctly)
+*/
+
+int8_t director_ecm(uint8_t *ecm, uint8_t *dw)
+{
+	uint8_t nanoType, nanoLength;
+	uint8_t *nanoData;
+	uint32_t pos = 3;
+	uint32_t entitlementId;
+	uint32_t ks[32];
+	uint8_t ecmKey[8];
+	uint16_t ecmLen = SCT_LEN(ecm);
+
+	if (ecmLen < 5)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	do
+	{
+		nanoType = ecm[pos];
+		nanoLength = ecm[pos + 1];
+
+		if (pos + 2 + nanoLength > ecmLen)
+		{
+			break;
+		}
+
+		nanoData = ecm + pos + 2;
+
+		// ECM validation
+		uint16_t payloadChecksum = (nanoData[nanoLength - 2] << 8) | nanoData[nanoLength - 1];
+		uint16_t calculatedChecksum = calculate_checksum(nanoData, nanoLength - 2);
+
+		if (calculatedChecksum != payloadChecksum)
+		{
+			cs_log_dbg(D_READER, "ECM checksum error (%.4X instead of %.4X)", calculatedChecksum, payloadChecksum);
+			return EMU_CHECKSUM_ERROR;
+		}
+		// End of ECM validation
+
+		switch (nanoType)
+		{
+			case 0xEC: // Director v6 (September 2017)
+			{
+				if (nanoLength != 0x28)
+				{
+					cs_log_dbg(D_READER, "WARNING: nanoType EC length (%d) != %d", nanoLength, 0x28);
+					break;
+				}
+
+				entitlementId = b2i(4, nanoData);
+				cs_log_dbg(D_READER, "INFO: Using entitlement id %.4X", entitlementId);
+
+				if (!get_key(entitlementId, "01", ecmKey, 8))
+				{
+					return EMU_KEY_NOT_FOUND;
+				}
+
+				// Step 1 - Decrypt DES CBC with ecmKey and iv = { 0 } (equal to nanoED)
+				uint8_t encryptedData[32] = { 0 };
+				memcpy(encryptedData, nanoData + 6, 32);
+
+				uint8_t iv[8] = { 0 };
+				des_cbc_decrypt(encryptedData, iv, ecmKey, 32);
+
+				uint8_t nanoMode = nanoData[5];
+
+				if ((nanoMode & 0x20) == 0) // Old algo
+				{
+					// Step 2 - Create CW (equal to nano ED)
+					dw[0] = encryptedData[0x05];
+					dw[1] = encryptedData[0x19];
+					dw[2] = encryptedData[0x1D];
+					dw[3] = (dw[0] + dw[1] + dw[2]) & 0xFF;
+					dw[4] = encryptedData[0x0B];
+					dw[5] = encryptedData[0x12];
+					dw[6] = encryptedData[0x1A];
+					dw[7] = (dw[4] + dw[5] + dw[6]) & 0xFF;
+					dw[8] = encryptedData[0x16];
+					dw[9] = encryptedData[0x03];
+					dw[10] = encryptedData[0x11];
+					dw[11] = (dw[8] + dw[9] + dw[10]) & 0xFF;
+					dw[12] = encryptedData[0x18];
+					dw[13] = encryptedData[0x10];
+					dw[14] = encryptedData[0x0E];
+					dw[15] = (dw[12] + dw[13] + dw[14]) & 0xFF;
+
+					return EMU_OK;
+				}
+				else // New algo (overencryption with AES)
+				{
+					// Step 2 - Prepare data for AES (it is like the creation of CW in nanoED but swapped each 8 bytes)
+					uint8_t dataEC[16] = { 0 };
+
+					dataEC[0] = encryptedData[0x02];
+					dataEC[1] = encryptedData[0x0E];
+					dataEC[2] = encryptedData[0x10];
+					dataEC[3] = encryptedData[0x18];
+					dataEC[4] = encryptedData[0x09];
+					dataEC[5] = encryptedData[0x11];
+					dataEC[6] = encryptedData[0x03];
+					dataEC[7] = encryptedData[0x16];
+
+					dataEC[8] = encryptedData[0x13];
+					dataEC[9] = encryptedData[0x1A];
+					dataEC[10] = encryptedData[0x12];
+					dataEC[11] = encryptedData[0x0B];
+					dataEC[12] = encryptedData[0x04];
+					dataEC[13] = encryptedData[0x1D];
+					dataEC[14] = encryptedData[0x19];
+					dataEC[15] = encryptedData[0x05];
+
+					// Step 3 - Decrypt AES CBC with new aesKey and iv 2EBD816A5E749A708AE45ADDD84333DE
+					uint8_t aesKeyIndex = nanoMode & 0x1F; // 32 possible AES keys
+					uint8_t aesKey[16] = { 0 };
+
+					char tmpBuffer[33];
+					cs_hexdump(0, aesKey, 16, tmpBuffer, sizeof(tmpBuffer));
+					cs_log_dbg(D_READER, "INFO: Using AES key index: %02X, value: %s", aesKeyIndex, tmpBuffer);
+
+					if (!get_key(aesKeyIndex, "AES", aesKey, 16))
+					{
+						return EMU_KEY_NOT_FOUND;
+					}
+
+					struct aes_keys aes;
+					aes_set_key(&aes, (char *)aesKey);
+
+					uint8_t ivAes[16] = { 0x2E, 0xBD, 0x81, 0x6A, 0x5E, 0x74, 0x9A, 0x70, 0x8A, 0xE4, 0x5A, 0xDD, 0xD8, 0x43, 0x33, 0xDE };
+					aes_cbc_decrypt(&aes, dataEC, 16, ivAes);
+
+					// Step 4 - Create CW (a simple swap)
+					uint8_t offset;
+					for (offset = 0; offset < 16; offset++)
+					{
+						dw[offset] = dataEC[15 - offset];
+					}
+
+					return EMU_OK;
+				}
+			}
+
+			case 0xED: // Director v5 (September 2016)
+			{
+				if (nanoLength != 0x26)
+				{
+					cs_log_dbg(D_READER, "WARNING: nanoType ED length (%d) != %d", nanoLength, 0x26);
+					break;
+				}
+
+				entitlementId = b2i(4, nanoData);
+				cs_log_dbg(D_READER, "INFO: Using entitlement id %.4X", entitlementId);
+
+				if (!get_key(entitlementId, "01", ecmKey, 8))
+				{
+					return EMU_KEY_NOT_FOUND;
+				}
+
+				uint8_t encryptedData[32] = { 0 };
+				memcpy(encryptedData, nanoData + 4, 32);
+
+				uint8_t iv[8] = { 0 };
+				des_cbc_decrypt(encryptedData, iv, ecmKey, 32);
+
+				dw[0] = encryptedData[0x05];
+				dw[1] = encryptedData[0x19];
+				dw[2] = encryptedData[0x1D];
+				dw[3] = (dw[0] + dw[1] + dw[2]) & 0xFF;
+				dw[4] = encryptedData[0x0B];
+				dw[5] = encryptedData[0x12];
+				dw[6] = encryptedData[0x1A];
+				dw[7] = (dw[4] + dw[5] + dw[6]) & 0xFF;
+				dw[8] = encryptedData[0x16];
+				dw[9] = encryptedData[0x03];
+				dw[10] = encryptedData[0x11];
+				dw[11] = (dw[8] + dw[9] + dw[10]) & 0xFF;
+				dw[12] = encryptedData[0x18];
+				dw[13] = encryptedData[0x10];
+				dw[14] = encryptedData[0x0E];
+				dw[15] = (dw[12] + dw[13] + dw[14]) & 0xFF;
+
+				return EMU_OK;
+			}
+
+			case 0xEE: // Director v4
+			{
+				if (nanoLength != 0x16)
+				{
+					cs_log_dbg(D_READER, "WARNING: nanoType EE length (%d) != %d", nanoLength, 0x16);
+					break;
+				}
+
+				entitlementId = b2i(4, nanoData);
+				cs_log_dbg(D_READER, "INFO: Using entitlement id %.4X", entitlementId);
+
+				if (!get_key(entitlementId, "01", ecmKey, 8))
+				{
+					return EMU_KEY_NOT_FOUND;
+				}
+
+				memcpy(dw, nanoData + 4 + 8, 8); // even
+				memcpy(dw + 8, nanoData + 4, 8); // odd
+
+				des_set_key(ecmKey, ks);
+
+				des(dw, ks, 0);
+				des(dw + 8, ks, 0);
+
+				dw[3] = (dw[0] + dw[1] + dw[2]) & 0xFF;
+				dw[7] = (dw[4] + dw[5] + dw[6]) & 0xFF;
+				dw[11] = (dw[8] + dw[9] + dw[10]) & 0xFF;
+				dw[15] = (dw[12] + dw[13] + dw[14]) & 0xFF;
+
+				return EMU_OK;
+			}
+
+			default:
+				cs_log_dbg(D_READER, "WARNING: nanoType %.2X not supported", nanoType);
+				return EMU_NOT_SUPPORTED;
+		}
+
+		pos += 2 + nanoLength;
+
+	} while (pos < ecmLen);
+
+	return EMU_NOT_SUPPORTED;
+}
+
+/*************************************************************************************************/
+
+/*
+ * Director EMM emulator
+ * Supported versions: v4, v5, v6 (same as v5)
+*/
+
+static const uint8_t MixTable[] =
+{
+	0x12, 0x78, 0x4B, 0x19, 0x13, 0x80, 0x2F, 0x84, 0x86, 0x4C, 0x09, 0x53, 0x15, 0x79, 0x6B, 0x49,
+	0x10, 0x4D, 0x33, 0x43, 0x18, 0x37, 0x83, 0x38, 0x82, 0x1B, 0x6E, 0x24, 0x2A, 0x85, 0x3C, 0x3D,
+	0x5A, 0x58, 0x55, 0x5D, 0x20, 0x41, 0x65, 0x51, 0x0C, 0x45, 0x63, 0x7F, 0x0F, 0x46, 0x21, 0x7C,
+	0x2C, 0x61, 0x7E, 0x0A, 0x42, 0x57, 0x35, 0x16, 0x87, 0x3B, 0x4F, 0x40, 0x34, 0x22, 0x26, 0x74,
+	0x32, 0x69, 0x44, 0x7A, 0x6A, 0x6D, 0x0D, 0x56, 0x23, 0x2B, 0x5C, 0x72, 0x76, 0x36, 0x28, 0x25,
+	0x2E, 0x52, 0x5B, 0x6C, 0x7D, 0x30, 0x0B, 0x5E, 0x47, 0x1F, 0x7B, 0x31, 0x3E, 0x11, 0x77, 0x1E,
+	0x60, 0x75, 0x54, 0x27, 0x50, 0x17, 0x70, 0x59, 0x1A, 0x2D, 0x4A, 0x67, 0x3A, 0x5F, 0x68, 0x08,
+	0x4E, 0x3F, 0x29, 0x6F, 0x81, 0x71, 0x39, 0x64, 0x48, 0x66, 0x73, 0x14, 0x0E, 0x1D, 0x62, 0x1C
+};
+
+/*
+static void rotate_bytes(uint8_t *in, int8_t n)
+{
+	if (n > 1)
+	{
+		uint8_t *e = in + n - 1;
+		do
+		{
+			uint8_t temp = *in;
+			*in++ = *e;
+			*e-- = temp;
+		}
+		while (in < e);
+	}
+}
+*/
+
+static void decrypt_ecm_key(uint8_t *emmKey, uint8_t *tagData, uint8_t *ecmKey)
+{
+	uint8_t temp, *e, *payLoad, iv[8] = { 0 };
+
+	//rotate_bytes(emmKey, 8);
+
+	e = emmKey + 8 - 1;
+	do
+	{
+		temp = *emmKey;
+		*emmKey++ = *e;
+		*e-- = temp;
+	}
+	while (emmKey < e);
+
+	payLoad = tagData + 4 + 5;
+	des_cbc_decrypt(payLoad, iv, emmKey, 16);
+
+	ecmKey[0] = payLoad[0x0F];
+	ecmKey[1] = payLoad[0x01];
+	ecmKey[2] = payLoad[0x0B];
+	ecmKey[3] = payLoad[0x03];
+	ecmKey[4] = payLoad[0x0E];
+	ecmKey[5] = payLoad[0x04];
+	ecmKey[6] = payLoad[0x0A];
+	ecmKey[7] = payLoad[0x08];
+}
+
+static int8_t parse_emm_nano_tags(uint8_t *data, uint32_t length, uint8_t keyIndex, uint32_t *keysAdded)
+{
+	uint8_t tagType, tagLength, *tagData, blockIndex, emmKey[8], tagDataDecrypted[16][8];
+	uint32_t pos = 0, entitlementId, ks[32];
+	int32_t i, k;
+	char keyValue[17];
+
+	if (length < 2)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	while (pos < length)
+	{
+		tagType = data[pos];
+		tagLength = data[pos+1];
+
+		if (pos + 2 + tagLength > length)
+		{
+			return EMU_CORRUPT_DATA;
+		}
+
+		tagData = data + pos + 2;
+
+		switch (tagType)
+		{
+			case 0xE4: // EMM_TAG_SECURITY_TABLE_DESCRIPTOR (ram emm keys)
+			{
+				uint8_t tagMode = data[pos + 2];
+
+				switch (tagMode)
+				{
+					case 0x01: // keySet 01 (MK01)
+					{
+						if (tagLength != 0x8A)
+						{
+							cs_log_dbg(D_READER, "WARNING: nanoTag E4 length (%d) != %d", tagLength, 0x8A);
+							return EMU_NOT_SUPPORTED;
+						}
+
+						if (!get_key(keyIndex, "MK01", emmKey, 8))
+						{
+							return EMU_KEY_NOT_FOUND;
+						}
+
+						uint8_t iv[8] = { 0 };
+						uint8_t *tagPayload = tagData + 2;
+						des_cbc_decrypt(tagPayload, iv, emmKey, 136);
+
+						for (k = 0; k < 16; k++) // loop 16 keys
+						{
+							for (i = 0; i < 8; i++) // loop 8 bytes of key
+							{
+								tagDataDecrypted[k][i] = tagPayload[MixTable[8 * k + i]];
+							}
+						}
+
+						blockIndex = tagData[1] & 0x03;
+
+						SAFE_MUTEX_LOCK(&emu_key_data_mutex);
+						for (i = 0; i < 16; i++)
+						{
+							emu_set_key('T', (blockIndex << 4) + i, "MK01", tagDataDecrypted[i], 8, 0, NULL, NULL);
+						}
+						SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
+					}
+					break;
+
+					case 0xFF: // keySet FF (MK)
+					{
+						if (tagLength != 0x82)
+						{
+							cs_log_dbg(D_READER, "WARNING: nanoTag E4 length (%d) != %d", tagLength, 0x82);
+							return EMU_NOT_SUPPORTED;
+						}
+
+						if (!get_key(keyIndex, "MK", emmKey, 8))
+						{
+							return EMU_KEY_NOT_FOUND;
+						}
+
+						des_set_key(emmKey, ks);
+
+						for (i = 0; i < 16; i++)
+						{
+							des(tagData + 2 + (i * 8), ks, 0);
+						}
+
+						blockIndex = tagData[1] & 0x03;
+
+						SAFE_MUTEX_LOCK(&emu_key_data_mutex);
+						for (i = 0; i < 16; i++)
+						{
+							emu_set_key('T', (blockIndex << 4) + i, "MK", tagData + 2 + (i * 8), 8, 0, NULL, NULL);
+						}
+						SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
+					}
+					break;
+
+					default:
+						cs_log_dbg(D_READER, "WARNING: nanoTag E4 mode %.2X not supported", tagMode);
+						return EMU_NOT_SUPPORTED;
+				}
+				break;
+			}
+
+			case 0xE1: // EMM_TAG_EVENT_ENTITLEMENT_DESCRIPTOR (ecm keys)
+			{
+				uint8_t tagMode = data[pos + 2 + 4];
+
+				switch (tagMode)
+				{
+					case 0x00: // ecm keys from mode FF
+					{
+						if (tagLength != 0x12)
+						{
+							cs_log_dbg(D_READER, "WARNING: nanoTag E1 length (%d) != %d", tagLength, 0x12);
+							return EMU_NOT_SUPPORTED;
+						}
+
+						entitlementId = b2i(4, tagData);
+
+						if (!get_key(keyIndex, "MK", emmKey, 8))
+						{
+							return EMU_KEY_NOT_FOUND;
+						}
+
+						des_set_key(emmKey, ks);
+						des(tagData + 4 + 5, ks, 0);
+						uint8_t ecmKeyChk[1] = { 0 };
+						memcpy(ecmKeyChk, tagData + 4 + 5 + 7, 1);
+
+						if (ecmKeyChk[0] != 0x00) // check if key looks valid (last byte 0x00)
+						{
+							cs_log_dbg(D_READER, "Key rejected from EMM (looks invalid)");
+							return EMU_KEY_REJECTED;
+						}
+
+						SAFE_MUTEX_LOCK(&emu_key_data_mutex);
+						if (emu_update_key('T', entitlementId, "01", tagData + 4 + 5, 8, 1, NULL))
+						{
+							(*keysAdded)++;
+							cs_hexdump(0, tagData + 4 + 5, 8, keyValue, sizeof(keyValue));
+							cs_log("Key found in EMM: T %.8X 01 %s", entitlementId, keyValue);
+						}
+						SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
+					}
+					break;
+
+					case 0x01: // ecm keys from mode 01
+					{
+						if (tagLength != 0x1A)
+						{
+							cs_log_dbg(D_READER, "WARNING: nanoTag E1 length (%d) != %d", tagLength, 0x1A);
+							return EMU_NOT_SUPPORTED;
+						}
+
+						entitlementId = b2i(4, tagData);
+
+						if (!get_key(keyIndex, "MK01", emmKey, 8))
+						{
+							return EMU_KEY_NOT_FOUND;
+						}
+
+						uint8_t ecmKey[8] = { 0 };
+						decrypt_ecm_key(emmKey, tagData, ecmKey);
+
+						if (ecmKey[7] != 0x00) // check if key looks valid (last byte 0x00)
+						{
+							cs_log_dbg(D_READER, "Key rejected from EMM (looks invalid)");
+							return EMU_KEY_REJECTED;
+						}
+
+						SAFE_MUTEX_LOCK(&emu_key_data_mutex);
+						if (emu_update_key('T', entitlementId, "01", ecmKey, 8, 1, NULL))
+						{
+							(*keysAdded)++;
+							cs_hexdump(0, ecmKey, 8, keyValue, sizeof(keyValue));
+							cs_log("Key found in EMM: T %.8X 01 %s", entitlementId, keyValue);
+						}
+						SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
+					}
+					break;
+
+					default:
+						cs_log_dbg(D_READER, "WARNING: nanoTag E1 mode %.2X not supported", tagMode);
+						return EMU_NOT_SUPPORTED;
+				}
+				break;
+			}
+
+			default:
+				cs_log_dbg(D_READER, "WARNING: nanoTag %.2X not supported", tagType);
+				return EMU_NOT_SUPPORTED;
+		}
+
+		pos += 2 + tagLength;
+	}
+
+	return EMU_OK;
+}
+
+static int8_t parse_emm_nano_data(uint8_t *data, uint32_t *nanoLength, uint32_t maxLength,
+									uint8_t keyIndex, uint32_t *keysAdded)
+{
+	uint32_t pos = 0;
+	uint16_t sectionLength;
+	int8_t ret = EMU_OK;
+
+	if (maxLength < 2)
+	{
+		(*nanoLength) = 0;
+		return EMU_NOT_SUPPORTED;
+	}
+
+	sectionLength = ((data[pos] << 8) | data[pos + 1]) & 0x0FFF;
+
+	if (pos + 2 + sectionLength > maxLength)
+	{
+		(*nanoLength) = pos;
+		return EMU_CORRUPT_DATA;
+	}
+
+	ret = parse_emm_nano_tags(data + pos + 2, sectionLength, keyIndex, keysAdded);
+
+	pos += 2 + sectionLength;
+
+	(*nanoLength) = pos;
+	return ret;
+}
+
+int8_t director_emm(uint8_t *emm, uint32_t *keysAdded)
+{
+	uint8_t keyIndex, ret = EMU_OK;
+	uint16_t emmLen = SCT_LEN(emm);
+	uint32_t pos = 3;
+	uint32_t permissionDataType;
+	uint32_t nanoLength = 0;
+
+	while (pos < emmLen && !ret)
+	{
+		permissionDataType = emm[pos];
+
+		switch (permissionDataType)
+		{
+			case 0x00:
+				break;
+
+			case 0x01:
+				pos += 0x0A;
+				break;
+
+			case 0x02:
+				pos += 0x26;
+				break;
+
+			default:
+				cs_log_dbg(D_READER, "ERROR: unknown permissionDataType %.2X (pos: %d)", permissionDataType, pos);
+				return EMU_NOT_SUPPORTED;
+		}
+
+		if (pos + 6 >= emmLen)
+		{
+			return EMU_CORRUPT_DATA;
+		}
+
+		keyIndex = emm[pos + 1];
+
+		// EMM validation
+		// Copy payload checksum bytes and then set them to zero,
+		// so they do not affect the calculated checksum.
+		uint16_t payloadChecksum = (emm[pos + 2] << 8) | emm[pos + 3];
+		memset(emm + pos + 2, 0, 2);
+		uint16_t calculatedChecksum = calculate_checksum(emm + 3, emmLen - 3);
+
+		if (calculatedChecksum != payloadChecksum)
+		{
+			cs_log_dbg(D_READER, "EMM checksum error (%.4X instead of %.4X)", calculatedChecksum, payloadChecksum);
+			return EMU_CHECKSUM_ERROR;
+		}
+		// End of EMM validation
+
+		pos += 0x04;
+		ret = parse_emm_nano_data(emm + pos, &nanoLength, emmLen - pos, keyIndex, keysAdded);
+		pos += nanoLength;
+	}
+
+	return ret;
+}
+
+#endif // WITH_EMU
diff --git a/module-emulator-director.h b/module-emulator-director.h
new file mode 100644
index 00000000..de430cf2
--- /dev/null
+++ b/module-emulator-director.h
@@ -0,0 +1,11 @@
+#ifndef MODULE_EMULATOR_DIRECTOR_H
+#define MODULE_EMULATOR_DIRECTOR_H
+
+#ifdef WITH_EMU
+
+int8_t director_ecm(uint8_t *ecm, uint8_t *dw);
+int8_t director_emm(uint8_t *emm, uint32_t *keysAdded);
+
+#endif // WITH_EMU
+
+#endif // MODULE_EMULATOR_DIRECTOR_H
diff --git a/module-emulator-irdeto.c b/module-emulator-irdeto.c
new file mode 100644
index 00000000..c9df9708
--- /dev/null
+++ b/module-emulator-irdeto.c
@@ -0,0 +1,602 @@
+#define MODULE_LOG_PREFIX "emu"
+
+#include "globals.h"
+
+#ifdef WITH_EMU
+
+#include "cscrypt/des.h"
+#include "module-emulator-osemu.h"
+#include "oscam-string.h"
+
+static inline void xxor(uint8_t *data, int32_t len, const uint8_t *v1, const uint8_t *v2)
+{
+	uint32_t i;
+
+	switch (len)
+	{
+		case 16:
+			for (i = 0; i < 16; ++i)
+			{
+				data[i] = v1[i] ^ v2[i];
+			}
+			break;
+
+		case 8:
+			for (i = 0; i < 8; ++i)
+			{
+				data[i] = v1[i] ^ v2[i];
+			}
+			break;
+
+		case 4:
+			for (i = 0; i < 4; ++i)
+			{
+				data[i] = v1[i] ^ v2[i];
+			}
+			break;
+
+		default:
+			while (len--)
+			{
+				*data++ = *v1++ ^ *v2++;
+			}
+			break;
+	}
+}
+
+// Irdeto EMU
+static int8_t get_key(uint8_t *buf, uint32_t ident, char keyName, uint32_t keyIndex,
+						uint8_t isCriticalKey, uint32_t *keyRef)
+{
+	char keyStr[EMU_MAX_CHAR_KEYNAME];
+
+	if (*keyRef > 0xFF)
+	{
+		return 0;
+	}
+
+	snprintf(keyStr, EMU_MAX_CHAR_KEYNAME, "%c%X", keyName, keyIndex);
+
+	if (emu_find_key('I', ident, 0, keyStr, buf, 16, *keyRef > 0 ? 0 : isCriticalKey, *keyRef, 0, NULL))
+	{
+		(*keyRef)++;
+		return 1;
+	}
+
+	return 0;
+}
+
+static void irdeto2_encrypt(uint8_t *data, const uint8_t *seed, const uint8_t *key, int32_t len)
+{
+	int32_t i;
+	const uint8_t *tmp = seed;
+	uint32_t ks1[32], ks2[32];
+
+	des_set_key(key, ks1);
+	des_set_key(key + 8, ks2);
+
+	len &= ~7;
+
+	for (i = 0; i + 7 < len; i += 8)
+	{
+		xxor(&data[i], 8, &data[i], tmp);
+		tmp = &data[i];
+		des(&data[i], ks1, 1);
+		des(&data[i], ks2, 0);
+		des(&data[i], ks1, 1);
+	}
+}
+
+static void irdeto2_decrypt(uint8_t *data, const uint8_t *seed, const uint8_t *key, int32_t len)
+{
+	int32_t i, n = 0;
+	uint8_t buf[2][8];
+	uint32_t ks1[32], ks2[32];
+
+	des_set_key(key, ks1);
+	des_set_key(key + 8, ks2);
+
+	len &= ~7;
+
+	memcpy(buf[n], seed, 8);
+
+	for (i = 0; i + 7 < len; i += 8, data += 8, n ^= 1)
+	{
+		memcpy(buf[1 - n], data, 8);
+		des(data, ks1, 0);
+		des(data, ks2, 1);
+		des(data, ks1, 0);
+		xxor(data, 8, data, buf[n]);
+	}
+}
+
+static int8_t calculate_hash(const uint8_t *key, const uint8_t *iv, const uint8_t *data, int32_t len)
+{
+	int32_t l, y;
+	uint8_t cbuff[32];
+	uint32_t ks1[32], ks2[32];
+
+	des_set_key(key, ks1);
+	des_set_key(key + 8, ks2);
+
+	memset(cbuff, 0, sizeof(cbuff));
+
+	len -= 8;
+
+	for (y = 0; y < len; y += 8)
+	{
+		if (y < len - 8)
+		{
+			xxor(cbuff, 8, cbuff, &data[y]);
+		}
+		else
+		{
+			l = len - y;
+			xxor(cbuff, l, cbuff, &data[y]);
+			xxor(cbuff + l, 8 - l, cbuff + l, iv + 8);
+		}
+
+		des(cbuff, ks1, 1);
+		des(cbuff, ks2, 0);
+		des(cbuff, ks1, 1);
+	}
+
+	return memcmp(cbuff, &data[len], 8) == 0;
+}
+
+int8_t irdeto2_ecm(uint16_t caid, uint8_t *oecm, uint8_t *dw)
+{
+	uint8_t keyNr = 0, length, end, key[16], okeySeed[16], keySeed[16], keyIV[16], tmp[16];
+	uint8_t ecmCopy[EMU_MAX_ECM_LEN], *ecm = oecm;
+	uint16_t ecmLen = SCT_LEN(ecm);
+	uint32_t key0Ref, keySeedRef, keyIVRef, ident, i, j, l;
+
+	if (ecmLen < 12)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	length = ecm[11];
+	keyNr = ecm[9];
+	ident = ecm[8] | caid << 8;
+
+	if (ecmLen < length + 12)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	key0Ref = 0;
+
+	while (get_key(key, ident, '0', keyNr, 1, &key0Ref))
+	{
+		keySeedRef = 0;
+
+		while (get_key(okeySeed, ident, 'M', 1, 1, &keySeedRef))
+		{
+			keyIVRef = 0;
+
+			while (get_key(keyIV, ident, 'M', 2, 1, &keyIVRef))
+			{
+				memcpy(keySeed, okeySeed, 16);
+				memcpy(ecmCopy, oecm, ecmLen);
+
+				ecm = ecmCopy;
+				memset(tmp, 0, 16);
+				irdeto2_encrypt(keySeed, tmp, key, 16);
+
+				ecm += 12;
+				irdeto2_decrypt(ecm, keyIV, keySeed, length);
+
+				i = (ecm[0] & 7) + 1;
+				end = length - 8 < 0 ? 0 : length - 8;
+
+				while (i < end)
+				{
+					l = ecm[i + 1] ? (ecm[i + 1] & 0x3F) + 2 : 1;
+
+					switch (ecm[i])
+					{
+						case 0x10:
+						case 0x50:
+							if (l == 0x13 && i <= length - 8 - l)
+							{
+								irdeto2_decrypt(&ecm[i + 3], keyIV, key, 16);
+							}
+							break;
+
+						case 0x78:
+							if (l == 0x14 && i <= length - 8 - l)
+							{
+								irdeto2_decrypt(&ecm[i + 4], keyIV, key, 16);
+							}
+							break;
+					}
+					i += l;
+				}
+
+				i = (ecm[0] & 7) + 1;
+
+				if (calculate_hash(keySeed, keyIV, ecm - 6, length + 6))
+				{
+					while (i < end)
+					{
+						l = ecm[i + 1] ? (ecm[i + 1] & 0x3F) + 2 : 1;
+
+						switch (ecm[i])
+						{
+							case 0x78:
+							{
+								if (l == 0x14 && i <= length - 8 - l)
+								{
+									memcpy(dw, &ecm[i + 4], 16);
+
+									for (j = 0; j < 16; j += 4) // fix dw checksum bytes
+									{
+										dw[j + 3] = (dw[j] + dw[j + 1] + dw[j + 2]) & 0xFF;
+									}
+									return EMU_OK;
+								}
+							}
+						}
+						i += l;
+					}
+				}
+			}
+
+			if (keyIVRef == 0)
+			{
+				return EMU_KEY_NOT_FOUND;
+			}
+		}
+
+		if (keySeedRef == 0)
+		{
+			return EMU_KEY_NOT_FOUND;
+		}
+	}
+
+	if (key0Ref == 0)
+	{
+		return EMU_KEY_NOT_FOUND;
+	}
+
+	return EMU_NOT_SUPPORTED;
+}
+
+// Irdeto2 EMM EMU
+static int8_t do_emm_type_op(uint32_t ident, uint8_t *emm, uint8_t *keySeed, uint8_t *keyIV, uint8_t *keyPMK,
+								uint16_t emmLen, uint8_t startOffset, uint8_t length, uint32_t *keysAdded)
+{
+	uint8_t tmp[16];
+	uint32_t end, i, l;
+	char keyName[EMU_MAX_CHAR_KEYNAME], keyValue[36];
+
+	memset(tmp, 0, 16);
+	irdeto2_encrypt(keySeed, tmp, keyPMK, 16);
+	irdeto2_decrypt(&emm[startOffset], keyIV, keySeed, length);
+
+	i = 16;
+	end = startOffset + (length - 8 < 0 ? 0 : length - 8);
+
+	while (i < end)
+	{
+		l = emm[i + 1] ? (emm[i + 1] & 0x3F) + 2 : 1;
+
+		switch (emm[i])
+		{
+			case 0x10:
+			case 0x50:
+				if (l == 0x13 && i <= startOffset + length - 8 - l)
+				{
+					irdeto2_decrypt(&emm[i + 3], keyIV, keyPMK, 16);
+				}
+				break;
+
+			case 0x78:
+				if (l == 0x14 && i <= startOffset + length - 8 - l)
+				{
+					irdeto2_decrypt(&emm[i + 4], keyIV, keyPMK, 16);
+				}
+				break;
+		}
+		i += l;
+	}
+
+	memmove(emm + 6, emm + 7, emmLen - 7);
+
+	i = 15;
+	end = startOffset + (length - 9 < 0 ? 0 : length - 9);
+
+	if (calculate_hash(keySeed, keyIV, emm + 3, emmLen - 4))
+	{
+		while (i < end)
+		{
+			l = emm[i + 1] ? (emm[i + 1] & 0x3F) + 2 : 1;
+
+			switch (emm[i])
+			{
+				case 0x10:
+				case 0x50:
+				{
+					if (l == 0x13 && i <= startOffset + length - 9 - l)
+					{
+						snprintf(keyName, EMU_MAX_CHAR_KEYNAME, "%02X", emm[i + 2] >> 2);
+						SAFE_MUTEX_LOCK(&emu_key_data_mutex);
+						emu_set_key('I', ident, keyName, &emm[i + 3], 16, 1, NULL, NULL);
+						SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
+
+						(*keysAdded)++;
+						cs_hexdump(0, &emm[i + 3], 16, keyValue, sizeof(keyValue));
+						cs_log("Key found in EMM: I %06X %s %s", ident, keyName, keyValue);
+					}
+				}
+			}
+			i += l;
+		}
+
+		if (*keysAdded > 0)
+		{
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+static int8_t do_emm_type_pmk(uint32_t ident, uint8_t *emm, uint8_t *keySeed, uint8_t *keyIV, uint8_t *keyPMK,
+								uint16_t emmLen, uint8_t startOffset, uint8_t length, uint32_t *keysAdded)
+{
+	uint32_t end, i, j, l;
+	char keyName[EMU_MAX_CHAR_KEYNAME], keyValue[36];
+
+	irdeto2_decrypt(&emm[startOffset], keyIV, keySeed, length);
+
+	i = 13;
+	end = startOffset + (length - 8 < 0 ? 0 : length - 8);
+
+	while (i < end)
+	{
+		l = emm[i + 1] ? (emm[i + 1] & 0x3F) + 2 : 1;
+
+		switch (emm[i])
+		{
+			case 0x10:
+			case 0x50:
+				if (l == 0x13 && i <= startOffset + length - 8 - l)
+				{
+					irdeto2_decrypt(&emm[i + 3], keyIV, keyPMK, 16);
+				}
+				break;
+
+			case 0x78:
+				if (l == 0x14 && i <= startOffset + length - 8 - l)
+				{
+					irdeto2_decrypt(&emm[i + 4], keyIV, keyPMK, 16);
+				}
+				break;
+
+			case 0x68:
+				if (l == 0x26 && i <= startOffset + length - 8 - l)
+				{
+					irdeto2_decrypt(&emm[i + 3], keyIV, keyPMK, 16 * 2);
+				}
+				break;
+		}
+		i += l;
+	}
+
+	memmove(emm + 7, emm + 9, emmLen - 9);
+
+	i = 11;
+	end = startOffset + (length - 10 < 0 ? 0 : length - 10);
+
+	if (calculate_hash(keySeed, keyIV, emm + 3, emmLen - 5))
+	{
+		while (i < end)
+		{
+			l = emm[i + 1] ? (emm[i + 1] & 0x3F) + 2 : 1;
+
+			switch (emm[i])
+			{
+				case 0x68:
+				{
+					if (l == 0x26 && i <= startOffset + length - 10 - l)
+					{
+						for (j = 0; j < 2; j++)
+						{
+							snprintf(keyName, EMU_MAX_CHAR_KEYNAME, "M%01X", 3 + j);
+							SAFE_MUTEX_LOCK(&emu_key_data_mutex);
+							emu_set_key('I', ident, keyName, &emm[i + 3 + j * 16], 16, 1, NULL, NULL);
+							SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
+
+							(*keysAdded)++;
+							cs_hexdump(0, &emm[i + 3 + j * 16], 16, keyValue, sizeof(keyValue));
+							cs_log("Key found in EMM: I %06X %s %s", ident, keyName, keyValue);
+						}
+					}
+				}
+			}
+			i += l;
+		}
+
+		if (*keysAdded > 0)
+		{
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+static const uint8_t fausto_xor[16] =
+{
+	0x22, 0x58, 0xBD, 0x85, 0x2E, 0x8E, 0x52, 0x80,
+	0xA3, 0x79, 0x98, 0x69, 0x68, 0xE2, 0xD8, 0x4D
+};
+
+int8_t irdeto2_emm(uint16_t caid, uint8_t *oemm, uint32_t *keysAdded)
+{
+	uint8_t length, okeySeed[16], keySeed[16], keyIV[16], keyPMK[16], startOffset, emmType;
+	uint8_t emmCopy[EMU_MAX_EMM_LEN], *emm = oemm;
+	uint16_t emmLen = SCT_LEN(emm);
+	uint32_t ident, keySeedRef, keyIVRef, keyPMK0Ref, keyPMK1Ref, keyPMK0ERef, keyPMK1ERef;
+
+	if (emmLen < 11)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	if (emm[3] == 0xC3 || emm[3] == 0xCB)
+	{
+		emmType = 2;
+		startOffset = 11;
+	}
+	else
+	{
+		emmType = 1;
+		startOffset = 10;
+	}
+
+	ident = emm[startOffset - 2] | caid << 8;
+	length = emm[startOffset - 1];
+
+	if (emmLen < length + startOffset)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	keySeedRef = 0;
+
+	while (get_key(okeySeed, ident, 'M', emmType == 1 ? 0 : 0xA, 1, &keySeedRef))
+	{
+		keyIVRef = 0;
+
+		while (get_key(keyIV, ident, 'M', 2, 1, &keyIVRef))
+		{
+			keyPMK0Ref = 0;
+			keyPMK1Ref = 0;
+			keyPMK0ERef = 0;
+			keyPMK1ERef = 0;
+
+			while (get_key(keyPMK, ident, 'M', emmType == 1 ? 3 : 0xB, 1, &keyPMK0Ref))
+			{
+				memcpy(keySeed, okeySeed, 16);
+				memcpy(emmCopy, oemm, emmLen);
+				emm = emmCopy;
+
+				if (emmType == 1)
+				{
+					if (do_emm_type_op(ident, emm, keySeed, keyIV, keyPMK, emmLen, startOffset, length, keysAdded) == 0)
+					{
+						return EMU_OK;
+					}
+				}
+				else
+				{
+					if (do_emm_type_pmk(ident, emm, keySeed, keyIV, keyPMK, emmLen, startOffset, length, keysAdded) == 0)
+					{
+						return EMU_OK;
+					}
+				}
+			}
+
+			if (emmType == 1)
+			{
+				while (get_key(keyPMK, ident, 'M', 4, 1, &keyPMK1Ref))
+				{
+					memcpy(keySeed, okeySeed, 16);
+					memcpy(emmCopy, oemm, emmLen);
+					emm = emmCopy;
+
+					if (do_emm_type_op(ident, emm, keySeed, keyIV, keyPMK, emmLen, startOffset, length, keysAdded) == 0)
+					{
+						return EMU_OK;
+					}
+				}
+
+				while (get_key(keyPMK, ident, 'M', 5, 1, &keyPMK0ERef))
+				{
+					xxor(keyPMK, 16, keyPMK, fausto_xor);
+					memcpy(keySeed, okeySeed, 16);
+					memcpy(emmCopy, oemm, emmLen);
+					emm = emmCopy;
+
+					if (do_emm_type_op(ident, emm, keySeed, keyIV, keyPMK, emmLen, startOffset, length, keysAdded) == 0)
+					{
+						return EMU_OK;
+					}
+				}
+
+				while (get_key(keyPMK, ident, 'M', 6, 1, &keyPMK1ERef))
+				{
+					xxor(keyPMK, 16, keyPMK, fausto_xor);
+					memcpy(keySeed, okeySeed, 16);
+					memcpy(emmCopy, oemm, emmLen);
+					emm = emmCopy;
+
+					if (do_emm_type_op(ident, emm, keySeed, keyIV, keyPMK, emmLen, startOffset, length, keysAdded) == 0)
+					{
+						return EMU_OK;
+					}
+				}
+			}
+
+			if (keyPMK0Ref == 0 && keyPMK1Ref == 0 && keyPMK0ERef == 0 && keyPMK1ERef == 0)
+			{
+				return EMU_KEY_NOT_FOUND;
+			}
+		}
+
+		if (keyIVRef == 0)
+		{
+			return EMU_KEY_NOT_FOUND;
+		}
+	}
+
+	if (keySeedRef == 0)
+	{
+		return 2;
+	}
+
+	return EMU_NOT_SUPPORTED;
+}
+
+int8_t irdeto2_get_hexserial(uint16_t caid, uint8_t *hexserial)
+{
+	uint32_t i, len;
+	KeyDataContainer *KeyDB;
+	KeyData *tmpKeyData;
+
+	KeyDB = emu_get_key_container('I');
+
+	if (KeyDB == NULL)
+	{
+		return 0;
+	}
+
+	for (i = 0; i < KeyDB->keyCount; i++)
+	{
+		if (KeyDB->EmuKeys[i].provider >> 8 != caid)
+		{
+			continue;
+		}
+
+		if (strcmp(KeyDB->EmuKeys[i].keyName, "MC"))
+		{
+			continue;
+		}
+
+		tmpKeyData = &KeyDB->EmuKeys[i];
+		len = tmpKeyData->keyLength;
+
+		if (len > 3)
+			{ len = 3; }
+
+		memcpy(hexserial + (3 - len), tmpKeyData->key, len);
+		return 1;
+	}
+
+	return 0;
+}
+
+#endif // WITH_EMU
diff --git a/module-emulator-irdeto.h b/module-emulator-irdeto.h
new file mode 100644
index 00000000..e13af0a0
--- /dev/null
+++ b/module-emulator-irdeto.h
@@ -0,0 +1,15 @@
+#ifndef MODULE_EMULATOR_IRDETO_H
+#define MODULE_EMULATOR_IRDETO_H
+
+#ifdef WITH_EMU
+
+int8_t irdeto2_ecm(uint16_t caid, uint8_t *oecm, uint8_t *dw);
+int8_t irdeto2_emm(uint16_t caid, uint8_t *oemm, uint32_t *keysAdded);
+
+// hexserial must be of type "uint8_t hexserial[3]"
+// returns 0 on error, 1 on success
+int8_t irdeto2_get_hexserial(uint16_t caid, uint8_t *hexserial);
+
+#endif // WITH_EMU
+
+#endif // MODULE_EMULATOR_IRDETO_H
diff --git a/module-emulator-nagravision.c b/module-emulator-nagravision.c
new file mode 100644
index 00000000..a0978713
--- /dev/null
+++ b/module-emulator-nagravision.c
@@ -0,0 +1,376 @@
+#define MODULE_LOG_PREFIX "emu"
+
+#include "globals.h"
+
+#ifdef WITH_EMU
+
+#include "cscrypt/bn.h"
+#include "cscrypt/des.h"
+#include "cscrypt/idea.h"
+#include "module-emulator-osemu.h"
+
+static void reverse_mem(uint8_t *in, int32_t len)
+{
+	uint8_t temp;
+	int32_t i;
+
+	for (i = 0; i < (len / 2); i++)
+	{
+		temp = in[i];
+		in[i] = in[len - i - 1];
+		in[len - i - 1] = temp;
+	}
+}
+
+static void reverse_mem_in_out(uint8_t *out, const uint8_t *in, int32_t n)
+{
+	if (n > 0)
+	{
+		out += n;
+		do
+		{
+			*(--out) = *(in++);
+		}
+		while (--n);
+	}
+}
+
+static int8_t rsa_input(BIGNUM *d, const uint8_t *in, int32_t n, int8_t le)
+{
+	int8_t result = 0;
+
+	if (le)
+	{
+		uint8_t *tmp = (uint8_t *)malloc(sizeof(uint8_t) * n);
+
+		if (tmp == NULL)
+		{
+			return 0;
+		}
+
+		reverse_mem_in_out(tmp, in, n);
+		result = BN_bin2bn(tmp, n, d) != 0;
+		free(tmp);
+	}
+	else
+	{
+		result = BN_bin2bn(in, n, d) != 0;
+	}
+
+	return result;
+}
+
+static int32_t rsa_output(uint8_t *out, int32_t n, BIGNUM *r, int8_t le)
+{
+	int32_t s = BN_num_bytes(r);
+
+	if (s > n)
+	{
+		uint8_t *buff = (uint8_t *)malloc(sizeof(uint8_t) * s);
+
+		if (buff == NULL)
+		{
+			return 0;
+		}
+
+		BN_bn2bin(r, buff);
+		memcpy(out, buff + s - n, n);
+		free(buff);
+	}
+	else if (s < n)
+	{
+		int32_t l = n - s;
+
+		memset(out, 0, l);
+		BN_bn2bin(r, out + l);
+	}
+	else
+	{
+		BN_bn2bin(r, out);
+	}
+
+	if (le)
+	{
+		reverse_mem(out, n);
+	}
+
+	return s;
+}
+
+static int32_t emu_rsa(uint8_t *out, const uint8_t *in, int32_t n, BIGNUM *exp, BIGNUM *mod, int8_t le)
+{
+	BN_CTX *ctx;
+	BIGNUM *r, *d;
+	int32_t result = 0;
+
+	ctx = BN_CTX_new();
+	r = BN_new();
+	d = BN_new();
+
+	if (rsa_input(d, in, n, le) && BN_mod_exp(r, d, exp, mod, ctx))
+	{
+		result = rsa_output(out, n, r, le);
+	}
+
+	BN_free(d);
+	BN_free(r);
+	BN_CTX_free(ctx);
+
+	return result;
+}
+
+// Nagra EMU
+
+static int8_t get_key(uint8_t *buf, uint32_t ident, char keyName, uint32_t keyIndex, uint8_t isCriticalKey)
+{
+	char keyStr[EMU_MAX_CHAR_KEYNAME];
+	snprintf(keyStr, EMU_MAX_CHAR_KEYNAME, "%c%X", keyName, keyIndex);
+
+	if (emu_find_key('N', ident, 0, keyStr, buf, keyName == 'M' ? 64 : 16, isCriticalKey, 0, 0, NULL))
+	{
+		return 1;
+	}
+
+	return 0;
+}
+
+static int8_t nagra2_signature(const uint8_t *vkey, const uint8_t *sig, const uint8_t *msg, int32_t len)
+{
+	uint8_t buff[16], iv[8];
+	int32_t i, j;
+
+	memcpy(buff, vkey, sizeof(buff));
+
+	for (i = 0; i + 7 < len; i += 8)
+	{
+		IDEA_KEY_SCHEDULE ek;
+
+		idea_set_encrypt_key(buff, &ek);
+		memcpy(buff, buff + 8, 8);
+		memset(iv, 0, sizeof(iv));
+		idea_cbc_encrypt(msg + i, buff + 8, 8, &ek, iv, IDEA_ENCRYPT);
+
+		for (j = 7; j >= 0; j--)
+		{
+			buff[j + 8] ^= msg[i + j];
+		}
+	}
+
+	buff[8] &= 0x7F;
+
+	return (memcmp(sig, buff + 8, 8) == 0);
+}
+
+static int8_t decrypt_ecm(uint8_t *in, uint8_t *out, const uint8_t *key, int32_t len,
+							const uint8_t *vkey, uint8_t *keyM)
+{
+	BIGNUM *exp, *mod;
+	uint8_t iv[8];
+	int32_t i = 0, sign = in[0] & 0x80;
+	uint8_t binExp = 3;
+	int8_t result = 1;
+
+	exp = BN_new();
+	mod = BN_new();
+	BN_bin2bn(&binExp, 1, exp);
+	BN_bin2bn(keyM, 64, mod);
+
+	if (emu_rsa(out, in + 1, 64, exp, mod, 1) <= 0)
+	{
+		BN_free(exp);
+		BN_free(mod);
+		return 0;
+	}
+
+	out[63] |= sign;
+
+	if (len > 64)
+	{
+		memcpy(out + 64, in + 65, len - 64);
+	}
+
+	memset(iv, 0, sizeof(iv));
+
+	if (in[0] & 0x04)
+	{
+		uint8_t key1[8], key2[8];
+
+		reverse_mem_in_out(key1, &key[0], 8);
+		reverse_mem_in_out(key2, &key[8], 8);
+
+		for (i = 7; i >= 0; i--)
+		{
+			reverse_mem(out + 8 * i, 8);
+		}
+
+		des_ede2_cbc_decrypt(out, iv, key1, key2, len);
+
+		for (i = 7; i >= 0; i--)
+		{
+			reverse_mem(out + 8 * i, 8);
+		}
+	}
+	else
+	{
+		IDEA_KEY_SCHEDULE ek;
+
+		idea_set_encrypt_key(key, &ek);
+		idea_cbc_encrypt(out, out, len & ~7, &ek, iv, IDEA_DECRYPT);
+	}
+
+	reverse_mem(out, 64);
+
+	if (result && emu_rsa(out, out, 64, exp, mod, 0) <= 0)
+	{
+		result = 0;
+	}
+
+	if (result && vkey && !nagra2_signature(vkey, out, out + 8, len - 8))
+	{
+		result = 0;
+	}
+
+	BN_free(exp);
+	BN_free(mod);
+	return result;
+}
+
+int8_t nagra2_ecm(uint8_t *ecm, uint8_t *dw)
+{
+	int8_t useVerifyKey = 0;
+	int32_t l = 0, s;
+
+	uint8_t cmdLen, ideaKeyNr, *dec, ideaKey[16], vKey[16], m1Key[64], mecmAlgo = 0;
+	uint16_t i = 0, ecmLen = SCT_LEN(ecm);
+	uint32_t ident, identMask, tmp1, tmp2, tmp3;
+
+	if (ecmLen < 8)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	cmdLen = ecm[4] - 5;
+	ident = (ecm[5] << 8) + ecm[6];
+	ideaKeyNr = (ecm[7] & 0x10) >> 4;
+
+	if (ideaKeyNr)
+	{
+		ideaKeyNr = 1;
+	}
+
+	if (ident == 1283 || ident == 1285 || ident == 1297)
+	{
+		ident = 1281;
+	}
+
+	if (cmdLen <= 63 || ecmLen < cmdLen + 10)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	if (!get_key(ideaKey, ident, '0', ideaKeyNr, 1))
+	{
+		return EMU_KEY_NOT_FOUND;
+	}
+
+	if (get_key(vKey, ident, 'V', 0, 0))
+	{
+		useVerifyKey = 1;
+	}
+
+	if (!get_key(m1Key, ident, 'M', 1, 1))
+	{
+		return EMU_KEY_NOT_FOUND;
+	}
+
+	reverse_mem(m1Key, 64);
+
+	dec = (uint8_t *)malloc(sizeof(uint8_t) * cmdLen);
+	if (dec == NULL)
+	{
+		return EMU_OUT_OF_MEMORY;
+	}
+
+	if (!decrypt_ecm(ecm + 9, dec, ideaKey, cmdLen, useVerifyKey ? vKey : 0, m1Key))
+	{
+		free(dec);
+		return EMU_NOT_SUPPORTED;
+	}
+
+	for (i = (dec[14] & 0x10) ? 16 : 20; i < cmdLen && l != 3; )
+	{
+		switch (dec[i])
+		{
+			case 0x10:
+			case 0x11:
+				if (i + 10 < cmdLen && dec[i + 1] == 0x09)
+				{
+					s = (~dec[i]) & 1;
+					mecmAlgo = dec[i + 2] & 0x60;
+					memcpy(dw + (s << 3), &dec[i + 3], 8);
+					i += 11;
+					l |= (s + 1);
+				}
+				else
+				{
+					i++;
+				}
+				break;
+
+			case 0x00:
+				i += 2;
+				break;
+
+			case 0x30:
+			case 0x31:
+			case 0x32:
+			case 0x33:
+			case 0x34:
+			case 0x35:
+			case 0x36:
+			case 0xB0:
+				if (i + 1 < cmdLen)
+				{
+					i += dec[i + 1] + 2;
+				}
+				else
+				{
+					i++;
+				}
+				break;
+
+			default:
+				i++;
+				continue;
+		}
+	}
+
+	free(dec);
+
+	if (l != 3)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	if (mecmAlgo > 0)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	identMask = ident & 0xFF00;
+
+	if (identMask == 0x1100 || identMask == 0x500 || identMask == 0x3100)
+	{
+		memcpy(&tmp1, dw, 4);
+		memcpy(&tmp2, dw + 4, 4);
+		memcpy(&tmp3, dw + 12, 4);
+		memcpy(dw, dw + 8, 4);
+		memcpy(dw + 4, &tmp3, 4);
+		memcpy(dw + 8, &tmp1, 4);
+		memcpy(dw + 12, &tmp2, 4);
+	}
+
+	return EMU_OK;
+}
+
+#endif // WITH_EMU
diff --git a/module-emulator-nagravision.h b/module-emulator-nagravision.h
new file mode 100644
index 00000000..31810eb0
--- /dev/null
+++ b/module-emulator-nagravision.h
@@ -0,0 +1,10 @@
+#ifndef MODULE_EMULATOR_NAGRAVISION_H
+#define MODULE_EMULATOR_NAGRAVISION_H
+
+#ifdef WITH_EMU
+
+int8_t nagra2_ecm(uint8_t *ecm, uint8_t *dw);
+
+#endif // WITH_EMU
+
+#endif // MODULE_EMULATOR_NAGRAVISION_H
diff --git a/module-emulator-omnicrypt.c b/module-emulator-omnicrypt.c
new file mode 100644
index 00000000..805926db
--- /dev/null
+++ b/module-emulator-omnicrypt.c
@@ -0,0 +1,72 @@
+#define MODULE_LOG_PREFIX "emu"
+
+#include "globals.h"
+
+#ifdef WITH_EMU
+
+#include "module-emulator-osemu.h"
+#include "module-emulator-omnicrypt.h"
+#include "oscam-aes.h"
+#include "oscam-string.h"
+
+
+static inline int8_t get_ecm_key(uint16_t provider, uint8_t parity, uint8_t *key)
+{
+	return emu_find_key('O', provider, 0, parity == 0 ? "00" : "01", key, 16, 1, 0, 0, NULL);
+}
+
+int8_t omnicrypt_ecm(uint8_t *ecm, uint8_t *dw)
+{
+	uint8_t section_syntax_indicator, session_key[16], session_key_parity, position;
+	uint16_t private_section_length, session_key_id, payload_length;
+	struct aes_keys aes;
+
+	section_syntax_indicator = ecm[1] >> 7;
+	if (section_syntax_indicator != 0) // The private_data_bytes immediately follow the private_section_length field
+	{
+		cs_log("ECM section syntax indicator %d not supported", section_syntax_indicator);
+		return EMU_NOT_SUPPORTED;
+	}
+
+	private_section_length = b2i(2, ecm + 1) & 0x0FFF;
+	if (private_section_length != 0x2D)
+	{
+		cs_log("ECM has an unsupported private section length of %d", private_section_length);
+		return EMU_NOT_SUPPORTED;
+	}
+
+	session_key_parity = ecm[3] & 0x01;
+	session_key_id = b2i(2, ecm + 4);
+
+	if (!get_ecm_key(session_key_id, session_key_parity, session_key))
+	{
+		return EMU_KEY_NOT_FOUND;
+	}
+	aes_set_key(&aes, (char *)session_key);
+
+	payload_length = b2i(2, ecm + 6) & 0x0FFF;
+	if (payload_length != 0x28)
+	{
+		cs_log("ECM has an unsupported payload length of %d", payload_length);
+		return EMU_NOT_SUPPORTED;
+	}
+
+	for (position = 8; position + 1 < payload_length; position += 4 + 16) // Run twice for odd, even CW
+	{
+		uint8_t parity = ecm[position + 1] & 0x01;
+		uint8_t length = ecm[position + 3];
+
+		if (length != 16)
+		{
+			cs_log("CW %d has an unsupported length of %d", parity, length);
+			return EMU_NOT_SUPPORTED;
+		}
+
+		aes_decrypt(&aes, ecm + position + 4, 16);
+		memcpy(dw + parity * 8, ecm + position + 4, 8); // Copy the first 8 bytes (rest are zeros)
+	}
+
+	return EMU_OK;
+}
+
+#endif // WITH_EMU
diff --git a/module-emulator-omnicrypt.h b/module-emulator-omnicrypt.h
new file mode 100644
index 00000000..3af8915c
--- /dev/null
+++ b/module-emulator-omnicrypt.h
@@ -0,0 +1,10 @@
+#ifndef MODULE_EMULATOR_OMNICRYPT_H
+#define MODULE_EMULATOR_OMNICRYPT_H
+
+#ifdef WITH_EMU
+
+int8_t omnicrypt_ecm(uint8_t *ecm, uint8_t *dw);
+
+#endif // WITH_EMU
+
+#endif // MODULE_EMULATOR_OMNICRYPT_H
diff --git a/module-emulator-osemu.c b/module-emulator-osemu.c
new file mode 100644
index 00000000..a3f8db3f
--- /dev/null
+++ b/module-emulator-osemu.c
@@ -0,0 +1,986 @@
+#define MODULE_LOG_PREFIX "emu"
+
+#include "globals.h"
+
+#ifdef WITH_EMU
+
+#include "oscam-string.h"
+#include "module-streamrelay.h"
+#include "module-emulator-osemu.h"
+#include "module-emulator-biss.h"
+#include "module-emulator-cryptoworks.h"
+#include "module-emulator-director.h"
+#include "module-emulator-irdeto.h"
+#include "module-emulator-nagravision.h"
+#include "module-emulator-omnicrypt.h"
+#include "module-emulator-powervu.h"
+#include "module-emulator-viaccess.h"
+
+// Shared functions
+
+int8_t is_valid_dcw(uint8_t *dw)
+{
+	uint8_t i;
+
+	for (i = 0; i < 8; i+= 4)
+	{
+		if (((dw[i] + dw[i + 1] + dw[i + 2]) & 0xFF) != dw[i + 3])
+		{
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+int8_t char_to_bin(uint8_t *out, const char *in, uint32_t inLen)
+{
+	uint32_t i, tmp;
+
+	for (i = 0; i < inLen / 2; i++)
+	{
+		if (sscanf(in + i * 2, "%02X", &tmp) != 1)
+		{
+			return 0;
+		}
+		out[i] = (uint8_t)tmp;
+	}
+	return 1;
+}
+
+void date_to_str(char *dateStr, uint8_t len, int8_t offset, uint8_t format)
+{
+	// Creates a formatted date string for use in various functions.
+	// A positive or negative time offset (in hours) can be set as well
+	// as the format of the output string.
+
+	time_t rawtime;
+	struct tm timeinfo;
+
+	time(&rawtime);
+	rawtime += (time_t) offset * 60 * 60; // Add a positive or negative offset
+	localtime_r(&rawtime, &timeinfo);
+
+	switch (format)
+	{
+		case 1:
+			strftime(dateStr, len, "%c", &timeinfo);
+			break;
+
+		case 2:
+			strftime(dateStr, len, "%F @ %R", &timeinfo);
+			break;
+
+		case 3:
+			strftime(dateStr, len, "%y%m%d%H", &timeinfo);
+			break;
+	}
+}
+
+/*
+ * Key DB
+ *
+ * The Emu reader gets keys from the OSCcam-Emu binary and the "SoftCam.Key" file.
+ *
+ * The keys are stored in structures of type "KeyDataContainer", one per CAS. Each
+ * container points to a dynamically allocated array of type "KeyData", which holds
+ * the actual keys. The array initially holds up to 64 keys (64 * KeyData), and it
+ * is expanded by 16 every time it's filled with keys. The "KeyDataContainer" also
+ * includes info about the number of keys it contains ("KeyCount") and the maximum
+ * number of keys it can store ("KeyMax").
+ *
+ * The "KeyData" structure, on the other hand, stores the actual key information,
+ * including the "identifier", "provider", "keyName", "key" and "keyLength". There
+ * is also a "nextKey" pointer to a similar "KeyData" structure which is only used
+ * for Irdeto multiple keys, in a linked list style structure. For all other CAS,
+ * the "nextKey" is a "NULL" pointer.
+ *
+ * For storing keys, the "SetKey" function is used. Duplicate keys are not allowed.
+ * When storing a key that is already present in the database, its "key" value is
+ * updated with the new one. For reading keys from the database, the "FindKey"
+ * function is used. To delete all keys in a container, the "DeleteKeysInContainer"
+ * function can be called.
+*/
+
+char *emu_keyfile_path = NULL;
+
+void emu_set_keyfile_path(const char *path)
+{
+	uint32_t pathLength;
+
+	if (emu_keyfile_path != NULL)
+	{
+		free(emu_keyfile_path);
+	}
+
+	pathLength = cs_strlen(path);
+	emu_keyfile_path = (char *)malloc(pathLength + 1);
+	if (emu_keyfile_path == NULL)
+	{
+		return;
+	}
+	cs_strncpy(emu_keyfile_path, path, pathLength + 1);
+}
+
+KeyDataContainer CwKeys = { NULL, 0, 0 };
+KeyDataContainer ViKeys = { NULL, 0, 0 };
+KeyDataContainer NagraKeys = { NULL, 0, 0 };
+KeyDataContainer IrdetoKeys = { NULL, 0, 0 };
+KeyDataContainer BissSWs = { NULL, 0, 0 };
+KeyDataContainer Biss2Keys = { NULL, 0, 0 };
+KeyDataContainer OmnicryptKeys = { NULL, 0, 0 };
+KeyDataContainer PowervuKeys = { NULL, 0, 0 };
+KeyDataContainer TandbergKeys = { NULL, 0, 0 };
+KeyDataContainer StreamKeys = { NULL, 0, 0 };
+
+KeyDataContainer *emu_get_key_container(char identifier)
+{
+	switch (identifier)
+	{
+		case 'W':
+			return &CwKeys;
+		case 'V':
+			return &ViKeys;
+		case 'N':
+			return &NagraKeys;
+		case 'I':
+			return &IrdetoKeys;
+		case 'F':
+			return &BissSWs;
+		case 'G':
+			return &Biss2Keys;
+		case 'O':
+			return &OmnicryptKeys;
+		case 'P':
+			return &PowervuKeys;
+		case 'T':
+			return &TandbergKeys;
+		case 'A':
+			return &StreamKeys;
+		default:
+			return NULL;
+	}
+}
+
+static void write_key_to_file(char identifier, uint32_t provider, const char *keyName, uint8_t *key,
+								uint32_t keyLength, char *comment)
+{
+	char line[1200], dateText[100], filename[EMU_KEY_FILENAME_MAX_LEN + 1];
+	char *path, *filepath, *keyValue;
+	uint32_t pathLength;
+	uint8_t fileNameLen = cs_strlen(EMU_KEY_FILENAME);
+	struct dirent *pDirent;
+	DIR *pDir;
+	FILE *file = NULL;
+
+	pathLength = cs_strlen(emu_keyfile_path);
+	path = (char *)malloc(pathLength + 1);
+	if (path == NULL)
+	{
+		return;
+	}
+	cs_strncpy(path, emu_keyfile_path, pathLength + 1);
+
+	pathLength = cs_strlen(path);
+	if (pathLength >= fileNameLen && strcasecmp(path + pathLength - fileNameLen, EMU_KEY_FILENAME) == 0)
+	{
+		// cut file name
+		path[pathLength - fileNameLen] = '\0';
+	}
+
+	pathLength = cs_strlen(path);
+	if (path[pathLength - 1] == '/' || path[pathLength - 1] == '\\')
+	{
+		// cut trailing /
+		path[pathLength - 1] = '\0';
+	}
+
+	pDir = opendir(path);
+	if (pDir == NULL)
+	{
+		cs_log("Cannot open key file path: %s", path);
+		free(path);
+		return;
+	}
+
+	while ((pDirent = readdir(pDir)) != NULL)
+	{
+		if (strcasecmp(pDirent->d_name, EMU_KEY_FILENAME) == 0)
+		{
+			cs_strncpy(filename, pDirent->d_name, sizeof(filename));
+			break;
+		}
+	}
+	closedir(pDir);
+
+	if (pDirent == NULL)
+	{
+		cs_strncpy(filename, EMU_KEY_FILENAME, sizeof(filename));
+	}
+
+	pathLength = cs_strlen(path) + 1 + cs_strlen(filename) + 1;
+	filepath = (char *)malloc(pathLength);
+	if (filepath == NULL)
+	{
+		free(path);
+		return;
+	}
+	snprintf(filepath, pathLength, "%s/%s", path, filename);
+	free(path);
+
+	cs_log("Writing key file: %s", filepath);
+
+	file = fopen(filepath, "a");
+	free(filepath);
+	if (file == NULL)
+	{
+		return;
+	}
+
+	date_to_str(dateText, sizeof(dateText), 0, 1);
+
+	keyValue = (char *)malloc((keyLength * 2) + 1);
+	if (keyValue == NULL)
+	{
+		fclose(file);
+		return;
+	}
+	cs_hexdump(0, key, keyLength, keyValue, (keyLength * 2) + 1);
+
+	if (comment)
+	{
+		snprintf(line, sizeof(line), "\n%c %08X %s %s ; added by Emu %s %s",
+					identifier, provider, keyName, keyValue, dateText, comment);
+	}
+	else
+	{
+		snprintf(line, sizeof(line), "\n%c %08X %s %s ; added by Emu %s",
+					identifier, provider, keyName, keyValue, dateText);
+	}
+
+	cs_log("Key written: %c %08X %s %s", identifier, provider, keyName, keyValue);
+
+	free(keyValue);
+
+	fwrite(line, cs_strlen(line), 1, file);
+	fclose(file);
+}
+
+int8_t emu_set_key(char identifier, uint32_t provider, char *keyName, uint8_t *orgKey, uint32_t keyLength,
+					uint8_t writeKey, char *comment, struct s_reader *rdr)
+{
+	uint32_t i, j;
+	uint8_t *tmpKey = NULL;
+	KeyDataContainer *KeyDB;
+	KeyData *tmpKeyData, *newKeyData;
+
+	identifier = (char)toupper((int)identifier);
+
+	KeyDB = emu_get_key_container(identifier);
+	if (KeyDB == NULL)
+	{
+		return 0;
+	}
+
+	keyName = strtoupper(keyName);
+
+	if (identifier == 'F') // Prepare BISS keys before saving to the db
+	{
+		// Convert legacy BISS "00" & "01" keynames
+		if (0 == strcmp(keyName, "00") || 0 == strcmp(keyName, "01"))
+		{
+			keyName = "00000000";
+		}
+
+		// All keyNames should have a length of 8 after converting
+		if (cs_strlen(keyName) != 8)
+		{
+			cs_log("WARNING: Wrong key format in %s: F %08X %s", EMU_KEY_FILENAME, provider, keyName);
+			return 0;
+		}
+
+		// Verify date-coded keyName (if enabled), ignoring old (expired) keys
+		if (rdr->emu_datecodedenabled)
+		{
+			char timeStr[9];
+			date_to_str(timeStr, sizeof(timeStr), 0, 3);
+
+			// Reject old date-coded keys, but allow our "00000000" evergreen label
+			if (strcmp("00000000", keyName) != 0 && strcmp(timeStr, keyName) >= 0)
+			{
+				return 0;
+			}
+		}
+	}
+
+	// Fix checksum for BISS keys with a length of 6
+	if (identifier == 'F' && keyLength == 6)
+	{
+		tmpKey = (uint8_t *)malloc(8 * sizeof(uint8_t));
+		if(tmpKey == NULL)
+		{
+			return 0;
+		}
+
+		tmpKey[0] = orgKey[0];
+		tmpKey[1] = orgKey[1];
+		tmpKey[2] = orgKey[2];
+		tmpKey[3] = ((orgKey[0] + orgKey[1] + orgKey[2]) & 0xFF);
+		tmpKey[4] = orgKey[3];
+		tmpKey[5] = orgKey[4];
+		tmpKey[6] = orgKey[5];
+		tmpKey[7] = ((orgKey[3] + orgKey[4] + orgKey[5]) & 0xFF);
+
+		keyLength = 8;
+	}
+	else // All keys with a length of 8, including BISS
+	{
+		tmpKey = (uint8_t *)malloc(keyLength * sizeof(uint8_t));
+		if (tmpKey == NULL)
+		{
+			return 0;
+		}
+
+		memcpy(tmpKey, orgKey, keyLength);
+	}
+
+	// Fix patched mgcamd format for Irdeto
+	if (identifier == 'I' && provider < 0xFFFF)
+	{
+		provider = provider << 8;
+	}
+
+	// Key already exists on db, update its value
+	for (i = 0; i < KeyDB->keyCount; i++)
+	{
+		if (KeyDB->EmuKeys[i].provider != provider)
+		{
+			continue;
+		}
+
+		// Don't match keyName (i.e. expiration date) for BISS1 and BISS2 mode 1/E sesssion words
+		if (identifier != 'F' && strcmp(KeyDB->EmuKeys[i].keyName, keyName))
+		{
+			continue;
+		}
+
+		// Allow multiple keys for Irdeto
+		if (identifier == 'I')
+		{
+			// Reject duplicates
+			tmpKeyData = &KeyDB->EmuKeys[i];
+			do
+			{
+				if (memcmp(tmpKeyData->key, tmpKey, tmpKeyData->keyLength < keyLength ? tmpKeyData->keyLength : keyLength) == 0)
+				{
+					free(tmpKey);
+					return 0;
+				}
+				tmpKeyData = tmpKeyData->nextKey;
+			}
+			while(tmpKeyData != NULL);
+
+			// Add new key
+			newKeyData = (KeyData *)malloc(sizeof(KeyData));
+			if (newKeyData == NULL)
+			{
+				free(tmpKey);
+				return 0;
+			}
+
+			newKeyData->identifier = identifier;
+			newKeyData->provider = provider;
+
+			if (cs_strlen(keyName) < EMU_MAX_CHAR_KEYNAME)
+			{
+				cs_strncpy(newKeyData->keyName, keyName, EMU_MAX_CHAR_KEYNAME);
+			}
+			else
+			{
+				memcpy(newKeyData->keyName, keyName, EMU_MAX_CHAR_KEYNAME);
+			}
+
+			newKeyData->keyName[EMU_MAX_CHAR_KEYNAME - 1] = 0;
+			newKeyData->key = tmpKey;
+			newKeyData->keyLength = keyLength;
+			newKeyData->nextKey = NULL;
+
+			tmpKeyData = &KeyDB->EmuKeys[i];
+			j = 0;
+
+			while (tmpKeyData->nextKey != NULL)
+			{
+				if (j == 0xFE)
+				{
+					break;
+				}
+				tmpKeyData = tmpKeyData->nextKey;
+				j++;
+			}
+
+			if (tmpKeyData->nextKey)
+			{
+				NULLFREE(tmpKeyData->nextKey->key);
+				NULLFREE(tmpKeyData->nextKey);
+			}
+			tmpKeyData->nextKey = newKeyData;
+
+			if (writeKey)
+			{
+				write_key_to_file(identifier, provider, keyName, tmpKey, keyLength, comment);
+			}
+		}
+		else // identifier != 'I'
+		{
+			free(KeyDB->EmuKeys[i].key);
+			KeyDB->EmuKeys[i].key = tmpKey;
+			KeyDB->EmuKeys[i].keyLength = keyLength;
+
+			if (identifier == 'F') // Update keyName (i.e. expiration date) for BISS
+			{
+				cs_strncpy(KeyDB->EmuKeys[i].keyName, keyName, EMU_MAX_CHAR_KEYNAME);
+			}
+
+			if (writeKey)
+			{
+				write_key_to_file(identifier, provider, keyName, tmpKey, keyLength, comment);
+			}
+		}
+		return 1;
+	}
+
+	// Key does not exist on db
+	if (KeyDB->keyCount + 1 > KeyDB->keyMax)
+	{
+		if (KeyDB->EmuKeys == NULL) // db is empty
+		{
+			KeyDB->EmuKeys = (KeyData *)malloc(sizeof(KeyData) * (KeyDB->keyMax + 64));
+			if (KeyDB->EmuKeys == NULL)
+			{
+				free(tmpKey);
+				return 0;
+			}
+			KeyDB->keyMax += 64;
+		}
+		else // db is full, expand it
+		{
+			tmpKeyData = (KeyData *)realloc(KeyDB->EmuKeys, sizeof(KeyData) * (KeyDB->keyMax + 16));
+			if (tmpKeyData == NULL)
+			{
+				free(tmpKey);
+				return 0;
+			}
+			KeyDB->EmuKeys = tmpKeyData;
+			KeyDB->keyMax += 16;
+		}
+	}
+
+	KeyDB->EmuKeys[KeyDB->keyCount].identifier = identifier;
+	KeyDB->EmuKeys[KeyDB->keyCount].provider = provider;
+
+	if (cs_strlen(keyName) < EMU_MAX_CHAR_KEYNAME)
+	{
+		cs_strncpy(KeyDB->EmuKeys[KeyDB->keyCount].keyName, keyName, EMU_MAX_CHAR_KEYNAME);
+	}
+	else
+	{
+		memcpy(KeyDB->EmuKeys[KeyDB->keyCount].keyName, keyName, EMU_MAX_CHAR_KEYNAME);
+	}
+
+	KeyDB->EmuKeys[KeyDB->keyCount].keyName[EMU_MAX_CHAR_KEYNAME - 1] = 0;
+	KeyDB->EmuKeys[KeyDB->keyCount].key = tmpKey;
+	KeyDB->EmuKeys[KeyDB->keyCount].keyLength = keyLength;
+	KeyDB->EmuKeys[KeyDB->keyCount].nextKey = NULL;
+	KeyDB->keyCount++;
+
+	if (writeKey)
+	{
+		write_key_to_file(identifier, provider, keyName, tmpKey, keyLength, comment);
+	}
+	return 1;
+}
+
+int8_t emu_find_key(char identifier, uint32_t provider, uint32_t providerIgnoreMask, char *keyName,
+					uint8_t *key, uint32_t maxKeyLength, uint8_t isCriticalKey, uint32_t keyRef,
+					uint8_t matchLength, uint32_t *getProvider)
+{
+	uint32_t i;
+	uint16_t j;
+	uint8_t provider_matching_key_count = 0;
+	KeyDataContainer *KeyDB;
+	KeyData *tmpKeyData;
+
+	KeyDB = emu_get_key_container(identifier);
+	if (KeyDB == NULL)
+	{
+		return 0;
+	}
+
+	for (i = 0; i < KeyDB->keyCount; i++)
+	{
+		if ((KeyDB->EmuKeys[i].provider & ~providerIgnoreMask) != provider)
+		{
+			continue;
+		}
+
+		// Don't match keyName (i.e. expiration date) for BISS
+		if (identifier != 'F' && strcmp(KeyDB->EmuKeys[i].keyName, keyName))
+		{
+			continue;
+		}
+
+		// "matchLength" cannot be used when multiple keys are allowed
+		// for a single provider/keyName combination.
+		// Currently this is the case only for Irdeto keys.
+		if (matchLength && KeyDB->EmuKeys[i].keyLength != maxKeyLength)
+		{
+			continue;
+		}
+
+		if (providerIgnoreMask)
+		{
+			if (provider_matching_key_count < keyRef)
+			{
+				provider_matching_key_count++;
+				continue;
+			}
+			else
+			{
+				keyRef = 0;
+			}
+		}
+
+		tmpKeyData = &KeyDB->EmuKeys[i];
+
+		j = 0;
+		while (j < keyRef && tmpKeyData->nextKey != NULL)
+		{
+			j++;
+			tmpKeyData = tmpKeyData->nextKey;
+		}
+
+		if (j == keyRef)
+		{
+			memcpy(key, tmpKeyData->key, tmpKeyData->keyLength > maxKeyLength ? maxKeyLength : tmpKeyData->keyLength);
+			if (tmpKeyData->keyLength < maxKeyLength)
+			{
+				memset(key + tmpKeyData->keyLength, 0, maxKeyLength - tmpKeyData->keyLength);
+			}
+
+			// Report the keyName (i.e. expiration date) of the session word found
+			if (identifier == 'F')
+			{
+				cs_strncpy(keyName, tmpKeyData->keyName, EMU_MAX_CHAR_KEYNAME);
+			}
+
+			if (getProvider != NULL)
+			{
+				(*getProvider) = tmpKeyData->provider;
+			}
+			return 1;
+		}
+		else
+		{
+			break;
+		}
+	}
+
+	if (isCriticalKey)
+	{
+		cs_log("Key not found: %c %X %s", identifier, provider, keyName);
+	}
+
+	return 0;
+}
+
+int8_t emu_update_key(char identifier, uint32_t provider, char *keyName, uint8_t *key,
+						uint32_t keyLength, uint8_t writeKey, char *comment)
+{
+	uint32_t keyRef = 0;
+	uint8_t *tmpKey = (uint8_t *)malloc(sizeof(uint8_t) * keyLength);
+
+	if (tmpKey == NULL)
+	{
+		return 0;
+	}
+
+	while (emu_find_key(identifier, provider, 0, keyName, tmpKey, keyLength, 0, keyRef, 0, NULL))
+	{
+		if (memcmp(tmpKey, key, keyLength) == 0)
+		{
+			free(tmpKey);
+			return 0;
+		}
+
+		keyRef++;
+	}
+
+	free(tmpKey);
+	return emu_set_key(identifier, provider, keyName, key, keyLength, writeKey, comment, NULL);
+}
+
+static int32_t delete_keys_in_container(char identifier)
+{
+	// Deletes all keys stored in memory for the specified identifier,
+	// but keeps the container itself, re-initialized at { NULL, 0, 0 }.
+	// Returns the count of deleted keys.
+
+	uint32_t oldKeyCount, i;
+	KeyData *tmpKeyData;
+	KeyDataContainer *KeyDB = emu_get_key_container(identifier);
+
+	if (KeyDB == NULL || KeyDB->EmuKeys == NULL || KeyDB->keyCount == 0)
+	{
+		return 0;
+	}
+
+	for (i = 0; i < KeyDB->keyCount; i++)
+	{
+		// For Irdeto multiple keys only (linked list structure)
+		while (KeyDB->EmuKeys[i].nextKey != NULL)
+		{
+			tmpKeyData = KeyDB->EmuKeys[i].nextKey;
+			KeyDB->EmuKeys[i].nextKey = KeyDB->EmuKeys[i].nextKey->nextKey;
+			free(tmpKeyData->key); // Free key
+			free(tmpKeyData); // Free KeyData
+		}
+
+		// For single keys (all identifiers, including Irdeto)
+		free(KeyDB->EmuKeys[i].key); // Free key
+	}
+
+	// Free the KeyData array
+	NULLFREE(KeyDB->EmuKeys);
+	oldKeyCount = KeyDB->keyCount;
+	KeyDB->keyCount = 0;
+	KeyDB->keyMax = 0;
+
+	return oldKeyCount;
+}
+
+void emu_clear_keydata(void)
+{
+	uint32_t total = 0;
+
+	total  = CwKeys.keyCount;
+	total += ViKeys.keyCount;
+	total += NagraKeys.keyCount;
+	total += IrdetoKeys.keyCount;
+	total += BissSWs.keyCount;
+	total += Biss2Keys.keyCount;
+	total += OmnicryptKeys.keyCount;
+	total += PowervuKeys.keyCount;
+	total += TandbergKeys.keyCount;
+	total += StreamKeys.keyCount;
+
+	if (total != 0)
+	{
+		cs_log("Freeing keys in memory: W:%d V:%d N:%d I:%d F:%d G:%d O:%d P:%d T:%d A:%d",
+				CwKeys.keyCount, ViKeys.keyCount, NagraKeys.keyCount, IrdetoKeys.keyCount, BissSWs.keyCount,
+				Biss2Keys.keyCount, OmnicryptKeys.keyCount, PowervuKeys.keyCount, TandbergKeys.keyCount,
+				StreamKeys.keyCount);
+
+		delete_keys_in_container('W');
+		delete_keys_in_container('V');
+		delete_keys_in_container('N');
+		delete_keys_in_container('I');
+		delete_keys_in_container('F');
+		delete_keys_in_container('G');
+		delete_keys_in_container('O');
+		delete_keys_in_container('P');
+		delete_keys_in_container('T');
+		delete_keys_in_container('A');
+	}
+}
+
+uint8_t emu_read_keyfile(struct s_reader *rdr, const char *opath)
+{
+	char line[1200], keyName[EMU_MAX_CHAR_KEYNAME], keyString[1026], identifier;
+	char *path, *filepath, filename[EMU_KEY_FILENAME_MAX_LEN + 1];
+	uint32_t pathLength, provider, keyLength;
+	uint8_t fileNameLen = cs_strlen(EMU_KEY_FILENAME);
+	uint8_t *key;
+	struct dirent *pDirent;
+	DIR *pDir;
+	FILE *file = NULL;
+
+	pathLength = cs_strlen(opath);
+	path = (char *)malloc(pathLength + 1);
+	if (path == NULL)
+	{
+		return 0;
+	}
+	cs_strncpy(path, opath, pathLength + 1);
+
+	pathLength = cs_strlen(path);
+	if (pathLength >= fileNameLen && strcasecmp(path + pathLength - fileNameLen, EMU_KEY_FILENAME) == 0)
+	{
+		// cut file name
+		path[pathLength - fileNameLen] = '\0';
+	}
+
+	pathLength = cs_strlen(path);
+	if (path[pathLength - 1] == '/' || path[pathLength - 1] == '\\')
+	{
+		// cut trailing /
+		path[pathLength - 1] = '\0';
+	}
+
+	pDir = opendir(path);
+	if (pDir == NULL)
+	{
+		cs_log("Cannot open key file path: %s", path);
+		free(path);
+		return 0;
+	}
+
+	while ((pDirent = readdir(pDir)) != NULL)
+	{
+		if (strcasecmp(pDirent->d_name, EMU_KEY_FILENAME) == 0)
+		{
+			cs_strncpy(filename, pDirent->d_name, sizeof(filename));
+			break;
+		}
+	}
+	closedir(pDir);
+
+	if (pDirent == NULL)
+	{
+		cs_log("Key file not found in: %s", path);
+		free(path);
+		return 0;
+	}
+
+	pathLength = cs_strlen(path) + 1 + cs_strlen(filename) + 1;
+	filepath = (char *)malloc(pathLength);
+	if (filepath == NULL)
+	{
+		free(path);
+		return 0;
+	}
+	snprintf(filepath, pathLength, "%s/%s", path, filename);
+	free(path);
+
+	cs_log("Reading key file: %s", filepath);
+
+	file = fopen(filepath, "r");
+	free(filepath);
+	if (file == NULL)
+	{
+		return 0;
+	}
+
+	emu_set_keyfile_path(opath);
+
+	while (fgets(line, 1200, file))
+	{
+		if (sscanf(line, "%c %8x %11s %1024s", &identifier, &provider, keyName, keyString) != 4)
+		{
+			continue;
+		}
+
+		keyLength = cs_strlen(keyString) / 2;
+		key = (uint8_t *)malloc(keyLength);
+		if (key == NULL)
+		{
+			fclose(file);
+			return 0;
+		}
+
+		if (char_to_bin(key, keyString, cs_strlen(keyString))) // Conversion OK
+		{
+			emu_set_key(identifier, provider, keyName, key, keyLength, 0, NULL, rdr);
+		}
+		else // Non-hex characters in keyString
+		{
+			if ((identifier != ';' && identifier != '#' && // Skip warning for comments, etc.
+				 identifier != '=' && identifier != '-' &&
+				 identifier != ' ') &&
+				!(identifier == 'F' && 0 == strncmp(keyString, "XXXXXXXXXXXX", 12))) // Skip warning for BISS 'Example key' lines
+			{
+				// Alert user regarding faulty line
+				cs_log("WARNING: non-hex value in %s at %c %08X %s %s",
+						EMU_KEY_FILENAME, identifier, provider, keyName, keyString);
+			}
+		}
+		free(key);
+	}
+	fclose(file);
+
+	return 1;
+}
+
+#if defined(WITH_SOFTCAM) && !defined(__APPLE__) && !defined(__ANDROID__)
+extern uint8_t SoftCamKey_Data[]    __asm__("_binary_SoftCam_Key_start");
+extern uint8_t SoftCamKey_DataEnd[] __asm__("_binary_SoftCam_Key_end");
+
+void emu_read_keymemory(struct s_reader *rdr)
+{
+	char *keyData, *line, *saveptr, keyName[EMU_MAX_CHAR_KEYNAME], keyString[1026], identifier;
+	uint32_t provider, keyLength;
+	uint8_t *key;
+
+	keyData = (char *)malloc(SoftCamKey_DataEnd - SoftCamKey_Data + 1);
+	if (keyData == NULL)
+	{
+		return;
+	}
+	memcpy(keyData, SoftCamKey_Data, SoftCamKey_DataEnd - SoftCamKey_Data);
+	keyData[SoftCamKey_DataEnd-SoftCamKey_Data] = 0x00;
+
+	line = strtok_r(keyData, "\n", &saveptr);
+	while (line != NULL)
+	{
+		if (sscanf(line, "%c %8x %11s %1024s", &identifier, &provider, keyName, keyString) != 4)
+		{
+			line = strtok_r(NULL, "\n", &saveptr);
+			continue;
+		}
+
+		keyLength = cs_strlen(keyString) / 2;
+
+		key = (uint8_t *)malloc(keyLength);
+		if (key == NULL)
+		{
+			free(keyData);
+			return;
+		}
+
+		if (char_to_bin(key, keyString, cs_strlen(keyString))) // Conversion OK
+		{
+			emu_set_key(identifier, provider, keyName, key, keyLength, 0, NULL, rdr);
+		}
+		else // Non-hex characters in keyString
+		{
+			if ((identifier != ';' && identifier != '#' && // Skip warning for comments, etc.
+				 identifier != '=' && identifier != '-' &&
+				 identifier != ' ') &&
+				!(identifier == 'F' && 0 == strncmp(keyString, "XXXXXXXXXXXX", 12))) // Skip warning for BISS 'Example key' lines
+			{
+				// Alert user regarding faulty line
+				cs_log("WARNING: non-hex value in internal keyfile at %c %08X %s %s",
+						identifier, provider, keyName, keyString);
+			}
+		}
+		free(key);
+		line = strtok_r(NULL, "\n", &saveptr);
+	}
+	free(keyData);
+}
+#else
+void emu_read_keymemory(struct s_reader *UNUSED(rdr)) { }
+#endif
+
+static const char *get_error_reason(int8_t result)
+{
+	switch (result)
+	{
+		case EMU_OK:
+			return "No error";
+
+		case EMU_NOT_SUPPORTED:
+			return "Not supported";
+
+		case EMU_KEY_NOT_FOUND:
+			return "Key not found";
+
+		case EMU_KEY_REJECTED:
+			return "ECM key rejected";
+
+		case EMU_CORRUPT_DATA:
+			return "Corrupt data";
+
+		case EMU_CW_NOT_FOUND:
+			return "CW not found";
+
+		case EMU_CHECKSUM_ERROR:
+			return "Checksum error";
+
+		case EMU_OUT_OF_MEMORY:
+			return "Out of memory";
+
+		default:
+			return "Unknown reason";
+	}
+}
+
+int8_t emu_process_ecm(struct s_reader *rdr, const ECM_REQUEST *er, uint8_t *cw, EXTENDED_CW *cw_ex)
+{
+	if (er->ecmlen < 3)
+	{
+		cs_log_dbg(D_TRACE, "Received ecm data of zero length!");
+		return 4;
+	}
+
+	uint16_t ecmLen = SCT_LEN(er->ecm);
+	uint8_t ecmCopy[ecmLen];
+	int8_t result = 1;
+
+	if (ecmLen != er->ecmlen)
+	{
+		cs_log_dbg(D_TRACE, "Actual ecm data length 0x%03X but ecm section length is 0x%03X",
+							er->ecmlen, ecmLen);
+		return 4;
+	}
+
+	if (ecmLen > EMU_MAX_ECM_LEN)
+	{
+		cs_log_dbg(D_TRACE, "Actual ecm data length 0x%03X but maximum supported ecm length is 0x%03X",
+							er->ecmlen, EMU_MAX_ECM_LEN);
+		return 1;
+	}
+
+	memcpy(ecmCopy, er->ecm, ecmLen);
+
+		 if (caid_is_viaccess(er->caid))    result = viaccess_ecm(ecmCopy, cw);
+	else if (caid_is_irdeto(er->caid))      result = irdeto2_ecm(er->caid, ecmCopy, cw);
+	else if (caid_is_cryptoworks(er->caid)) result = cryptoworks_ecm(er->caid, ecmCopy, cw);
+	else if (caid_is_powervu(er->caid))
+	{
+#ifdef MODULE_STREAMRELAY
+		result = powervu_ecm(ecmCopy, cw, cw_ex, er->srvid, er->caid, er->tsid, er->onid, er->ens, NULL);
+#else
+		result = powervu_ecm(ecmCopy, cw, cw_ex, er->srvid, er->caid, er->tsid, er->onid, er->ens);
+#endif
+	}
+	else if (caid_is_director(er->caid))    result = director_ecm(ecmCopy, cw);
+	else if (caid_is_nagra(er->caid))       result = nagra2_ecm(ecmCopy, cw);
+	else if (caid_is_biss(er->caid))        result = biss_ecm(rdr, er->ecm, er->caid, er->pid, cw, cw_ex);
+	else if (er->caid == 0x00FF)			result = omnicrypt_ecm(ecmCopy, cw); // temp caid
+
+	if (result != 0)
+	{
+		cs_log("ECM failed: %s", get_error_reason(result));
+	}
+
+	return result;
+}
+
+int8_t emu_process_emm(struct s_reader *rdr, uint16_t caid, const uint8_t *emm, uint32_t *keysAdded)
+{
+	uint16_t emmLen = SCT_LEN(emm);
+	uint8_t emmCopy[emmLen];
+	int8_t result = 1;
+
+	if (emmLen > EMU_MAX_EMM_LEN)
+	{
+		return 1;
+	}
+	memcpy(emmCopy, emm, emmLen);
+	*keysAdded = 0;
+
+	     if (caid_is_viaccess(caid))     result = viaccess_emm(emmCopy, keysAdded);
+	else if (caid_is_irdeto(caid))       result = irdeto2_emm(caid, emmCopy, keysAdded);
+	else if (caid_is_powervu(caid))      result = powervu_emm(emmCopy, keysAdded);
+	else if (caid_is_director(caid))     result = director_emm(emmCopy, keysAdded);
+	else if (caid_is_biss_dynamic(caid)) result = biss_emm(rdr, emmCopy, keysAdded);
+
+	if (result != 0)
+	{
+		cs_log_dbg(D_EMM,"EMM failed: %s", get_error_reason(result));
+	}
+
+	return result;
+}
+
+#endif // WITH_EMU
diff --git a/module-emulator-osemu.h b/module-emulator-osemu.h
new file mode 100644
index 00000000..a83dc64a
--- /dev/null
+++ b/module-emulator-osemu.h
@@ -0,0 +1,96 @@
+#ifndef MODULE_EMULATOR_OSEMU_H_
+#define MODULE_EMULATOR_OSEMU_H_
+
+#ifdef WITH_EMU
+
+// Version info
+#define EMU_VERSION 802
+
+#define EMU_MAX_CHAR_KEYNAME 12
+#define EMU_KEY_FILENAME "SoftCam.Key"
+#define EMU_KEY_FILENAME_MAX_LEN 31
+#define EMU_MAX_ECM_LEN MAX_ECM_SIZE
+#define EMU_MAX_EMM_LEN MAX_EMM_SIZE
+
+/*
+ * Error codes for ProccessECM and ProccessEMM functions
+ *  0 - OK
+ *  1 - ECM / EMM not supported
+ *  2 - ECM / EMM key not found
+ *  3 - ECM key rejected
+ *  4 - Corrupt data
+ *  5 - CW not found
+ *  6 - CW / ECM / EMM checksum error
+ *  7 - Out of memory
+*/
+
+#define EMU_OK             0
+#define EMU_NOT_SUPPORTED  1
+#define EMU_KEY_NOT_FOUND  2
+#define EMU_KEY_REJECTED   3
+#define EMU_CORRUPT_DATA   4
+#define EMU_CW_NOT_FOUND   5
+#define EMU_CHECKSUM_ERROR 6
+#define EMU_OUT_OF_MEMORY  7
+
+typedef struct KeyData KeyData;
+
+struct KeyData
+{
+	char identifier;
+	uint32_t provider;
+	char keyName[EMU_MAX_CHAR_KEYNAME];
+	uint8_t *key;
+	uint32_t keyLength;
+	KeyData *nextKey;
+};
+
+typedef struct
+{
+	KeyData *EmuKeys;
+	uint32_t keyCount;
+	uint32_t keyMax;
+} KeyDataContainer;
+
+extern KeyDataContainer CwKeys;
+extern KeyDataContainer ViKeys;
+extern KeyDataContainer NagraKeys;
+extern KeyDataContainer IrdetoKeys;
+extern KeyDataContainer BissSWs;      // 'F' identifier - BISS1 and BISS2 mode 1/E session words
+extern KeyDataContainer Biss2Keys;    // 'G' identifier - BISS2 mode CA session keys (ECM keys)
+extern KeyDataContainer OmnicryptKeys;
+extern KeyDataContainer PowervuKeys;
+extern KeyDataContainer TandbergKeys;
+extern KeyDataContainer StreamKeys;
+extern uint8_t viasat_const[];
+extern char *emu_keyfile_path;
+extern pthread_mutex_t emu_key_data_mutex;
+
+void emu_set_keyfile_path(const char *path);
+void emu_clear_keydata(void);
+uint8_t emu_read_keyfile(struct s_reader *rdr, const char *path);
+void emu_read_keymemory(struct s_reader *rdr);
+
+int8_t is_valid_dcw(uint8_t *dw);
+int8_t char_to_bin(uint8_t *out, const char *in, uint32_t inLen);
+void date_to_str(char *dateStr, uint8_t len, int8_t offset, uint8_t format);
+
+KeyDataContainer *emu_get_key_container(char identifier);
+
+int8_t emu_process_ecm(struct s_reader *rdr, const ECM_REQUEST *er, uint8_t *cw, EXTENDED_CW* cw_ex);
+
+int8_t emu_process_emm(struct s_reader *rdr, uint16_t caid, const uint8_t *emm, uint32_t *keysAdded);
+
+int8_t emu_find_key(char identifier, uint32_t provider, uint32_t providerIgnoreMask, char *keyName,
+					uint8_t *key, uint32_t maxKeyLength, uint8_t isCriticalKey, uint32_t keyRef,
+					uint8_t matchLength, uint32_t *getProvider);
+
+int8_t emu_set_key(char identifier, uint32_t provider, char *keyName, uint8_t *orgKey, uint32_t keyLength,
+					uint8_t writeKey, char *comment, struct s_reader *rdr);
+
+int8_t emu_update_key(char identifier, uint32_t provider, char *keyName, uint8_t *key, uint32_t keyLength,
+						uint8_t writeKey, char *comment);
+
+#endif // WITH_EMU
+
+#endif // MODULE_EMULATOR_H_
diff --git a/module-emulator-powervu.c b/module-emulator-powervu.c
new file mode 100644
index 00000000..810fcecf
--- /dev/null
+++ b/module-emulator-powervu.c
@@ -0,0 +1,2795 @@
+#define MODULE_LOG_PREFIX "emu"
+
+#include "globals.h"
+
+#ifdef WITH_EMU
+
+#include "cscrypt/des.h"
+#include "module-streamrelay.h"
+#include "module-emulator-osemu.h"
+#include "module-emulator-powervu.h"
+#include "oscam-string.h"
+#include "oscam-time.h"
+
+static inline uint8_t get_bit(uint8_t byte, uint8_t bitnb)
+{
+	return ((byte & (1 << bitnb)) ? 1 : 0);
+}
+
+static inline uint8_t set_bit(uint8_t val, uint8_t bitnb, uint8_t biton)
+{
+	return (biton ? (val | (1 << bitnb)) : (val & ~(1 << bitnb)));
+}
+
+static uint8_t crc8_calc(uint8_t *data, int len)
+{
+	int i;
+	uint8_t crc = 0;
+	uint8_t crcTable[256] =
+	{
+		0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
+		0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
+		0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
+		0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
+		0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
+		0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
+		0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
+		0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
+		0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
+		0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
+		0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
+		0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
+		0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
+		0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
+		0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
+		0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
+	};
+
+	for (i = 0; i < len; i++)
+	{
+		crc = crcTable[data[i] ^ crc];
+	}
+
+	return crc;
+}
+
+static void pad_data(uint8_t *data, int len, uint8_t *dataPadded)
+{
+	int i;
+	uint8_t pad[] =
+	{
+		0x01, 0x02, 0x22, 0x04, 0x20, 0x2A, 0x1F, 0x03,
+		0x04, 0x06, 0x02, 0x0C, 0x2B, 0x2B, 0x01, 0x7B
+	};
+
+	for (i = 0; i < len; i++)
+	{
+		dataPadded[i] = data[i];
+	}
+
+	dataPadded[len] = 0x01;
+
+	for (i = len + 1; i < 0x2F; i++)
+	{
+		dataPadded[i] = 0x00;
+	}
+
+	dataPadded[0x2F] = len;
+
+	for (i = 0; i < 16; i++)
+	{
+		dataPadded[0x30 + i] = pad[i];
+	}
+}
+
+static void hash_mode_01_custom_md5(uint8_t *data, uint8_t *hash)
+{
+	int i, j, s;
+	uint32_t a, b, c, d, f = 0, g;
+
+	uint32_t T[] =
+	{
+		0x783E16F6, 0xC267AC13, 0xA2B17F12, 0x6B8A31A4,
+		0xF910654D, 0xB702DBCB, 0x266CEF60, 0x5145E47C,
+		0xB92E00D6, 0xE80A4A64, 0x8A07FA77, 0xBA7D89A9,
+		0xEBED8022, 0x653AAF2B, 0xF118B03B, 0x6CC16544,
+		0x96EB6583, 0xF4E27E35, 0x1ABB119E, 0x068D3EF2,
+		0xDAEAA8A5, 0x3C312A3D, 0x59538388, 0xA100772F,
+		0xAB0165CE, 0x979959E7, 0x5DD8F53D, 0x189662BA,
+		0xFD021A9C, 0x6BC2D338, 0x1EFF667E, 0x40C66888,
+		0x6E9F07FF, 0x0CEF442F, 0x82D20190, 0x4E8CAEAC,
+		0x0F7CB305, 0x2E73FBE7, 0x1CE884A2, 0x7A60BD52,
+		0xC348B30D, 0x081CE3AA, 0xA12220E7, 0x38C7EC79,
+		0xCBD8DD3A, 0x62B4FBA5, 0xAD2A63DB, 0xE4D0852E,
+		0x53DE980F, 0x9C8DDA59, 0xA6B4CEDE, 0xB48A7692,
+		0x0E2C46A4, 0xEB9367CB, 0x165D72EE, 0x75532B45,
+		0xB9CA8E97, 0x08C8837B, 0x966F917B, 0x527515B4,
+		0xF27A5E5D, 0xB71E6267, 0x7603D7E6, 0x9837DD69
+	}; // CUSTOM T
+
+	uint8_t r[] =
+	{
+		0x06, 0x0A, 0x0F, 0x15, 0x05, 0x09, 0x0E, 0x14,
+		0x04, 0x0B, 0x10, 0x17, 0x07, 0x0C, 0x11, 0x16
+	}; // STANDARD REORDERED
+
+	uint8_t tIdxInit[] = { 0, 1, 5, 0 }; // STANDARD
+	uint8_t tIdxIncr[] = { 1, 5, 3, 7 }; // STANDARD
+
+	uint32_t h[] = { 0xEAD81D2E, 0xCE4DC6E9, 0xF9B5C301, 0x10325476 }; // CUSTOM h0, h1, h2, STANDARD h3
+	uint32_t dataLongs[16];
+
+	for (i = 0; i < 16; i++)
+	{
+		dataLongs[i] = (data[4 * i + 0] << 0) + (data[4 * i + 1] << 8) +
+						(data[4 * i + 2] << 16) + (data[4 * i + 3] << 24);
+	}
+
+	a = h[0];
+	b = h[1];
+	c = h[2];
+	d = h[3];
+
+	for (i = 0; i < 4; i++)
+	{
+		g = tIdxInit[i];
+
+		for (j = 0; j < 16; j++)
+		{
+			if (i == 0)
+			{
+				f = (b & c) | (~b & d);
+			}
+			else if (i == 1)
+			{
+				f = (b & d) | (~d & c);
+			}
+			else if (i == 2)
+			{
+				f = (b ^ c ^ d);
+			}
+			else if (i == 3)
+			{
+				f = (~d | b) ^ c;
+			}
+
+			f = dataLongs[g] + a + T[16 * i + j] + f;
+
+			s = r[4 * i + (j & 3)];
+			f = (f << s) | (f >> (32 - s));
+
+			a = d;
+			d = c;
+			c = b;
+			b += f;
+
+			g = (g + tIdxIncr[i]) & 0xF;
+		}
+	}
+
+	h[0] += a;
+	h[1] += b;
+	h[2] += c;
+	h[3] += d;
+
+	for (i = 0; i < 4; i++)
+	{
+		hash[4 * i + 0] = h[i] >> 0;
+		hash[4 * i + 1] = h[i] >> 8;
+		hash[4 * i + 2] = h[i] >> 16;
+		hash[4 * i + 3] = h[i] >> 24;
+	}
+}
+
+static void hash_mode_02(uint8_t *data, uint8_t *hash)
+{
+	int i;
+	uint32_t a, b, c, d, e, f = 0, tmp;
+	uint32_t h[] = { 0x81887F3A, 0x36CCA480, 0x99056FB1, 0x79705BAE };
+	uint32_t dataLongs[80];
+
+	for (i = 0; i < 16; i++)
+	{
+		dataLongs[i] = (data[4 * i + 0] << 24) + (data[4 * i + 1] << 16) +
+						(data[4 * i + 2] << 8) + (data[4 * i + 3] << 0);
+	}
+
+	for (i = 0; i < 64; i++)
+	{
+		dataLongs[16 + i] = dataLongs[16 + i - 2];
+		dataLongs[16 + i] ^= dataLongs[16 + i - 7];
+		dataLongs[16 + i] ^= dataLongs[16 + i - 13];
+		dataLongs[16 + i] ^= dataLongs[16 + i - 16];
+	}
+
+	a = dataLongs[0];
+	b = dataLongs[1];
+	c = dataLongs[2];
+	d = dataLongs[3];
+	e = dataLongs[4];
+
+	for (i = 0; i < 80; i++)
+	{
+		if (i < 0x15) f = (b & c) | (~b & d);
+		else if (i < 0x28) f = (b ^ c ^ d);
+		else if (i < 0x3D) f = (b & c) | (c & d) | (b & d);
+		else if (i < 0x50) f = (b ^ c ^ d);
+
+		tmp = a;
+		a = e + f + (a << 5) + (a >> 27) + h[i / 0x14] + dataLongs[i];
+		e = d;
+		d = c;
+		c = (b << 30) + (b >> 2);
+		b = tmp;
+	}
+
+	dataLongs[0] += a;
+	dataLongs[1] += b;
+	dataLongs[2] += c;
+	dataLongs[3] += d;
+
+	for (i = 0; i < 4; i++)
+	{
+		hash[4 * i + 0] = dataLongs[i] >> 24;
+		hash[4 * i + 1] = dataLongs[i] >> 16;
+		hash[4 * i + 2] = dataLongs[i] >> 8;
+		hash[4 * i + 3] = dataLongs[i] >> 0;
+	}
+}
+
+static void hash_mode_03(uint8_t *data, uint8_t *hash)
+{
+	int i, j, k, s, s2, tmp;
+	uint32_t a, b, c, d, f = 0, g;
+	uint32_t a2, b2, c2, d2, f2 = 0, g2;
+
+	uint32_t T[] = { 0xC88F3F2E, 0x967506BA, 0xDA877A7B, 0x0DECCDFE };
+	uint32_t T2[] = { 0x01F42668, 0x39C7CDA5, 0xD490E2FE, 0x9965235D };
+
+	uint8_t r[] =
+	{
+		0x0B, 0x0E, 0x0F, 0x0C, 0x05, 0x08, 0x07, 0x09,
+		0x0B, 0x0D, 0x0E, 0x0F, 0x06, 0x07, 0x09, 0x08,
+		0x07, 0x06, 0x08, 0x0D, 0x0B, 0x09, 0x07, 0x0F,
+		0x07, 0x0C, 0x0F, 0x09, 0x0B, 0x07, 0x0D, 0x0C
+	};
+
+	uint8_t tIdxIncr[] =
+	{
+		0x07, 0x04, 0x0D, 0x01, 0x0A, 0x06, 0x0F, 0x03,
+		0x0C, 0x00, 0x09, 0x05, 0x02, 0x0E, 0x0B, 0x08,
+		0x05, 0x0D, 0x02, 0x00, 0x04, 0x09, 0x03, 0x08,
+		0x01, 0x0A, 0x07, 0x0B, 0x06, 0x0F, 0x0C, 0x0E
+	};
+
+	uint32_t h[] = { 0xC8616857, 0x9D3F5B8E, 0x4D7B8F76, 0x97BC8D80 };
+
+	uint32_t dataLongs[80];
+	uint32_t result[4];
+
+	for (i = 0; i < 16; i++)
+	{
+		dataLongs[i] = (data[4 * i + 0] << 24) + (data[4 * i + 1] << 16) +
+						(data[4 * i + 2] << 8) + (data[4 * i + 3] << 0);
+	}
+
+	a = h[0];
+	b = h[1];
+	c = h[2];
+	d = h[3];
+
+	a2 = h[3];
+	b2 = h[2];
+	c2 = h[1];
+	d2 = h[0];
+
+	for (i = 0; i < 4; i++)
+	{
+		for (j = 0; j < 16; j++)
+		{
+			tmp = j;
+
+			for (k = 0; k < i; k++)
+			{
+				tmp = tIdxIncr[tmp];
+			}
+
+			g = 0x0F - tmp;
+			g2 = tmp;
+
+			if (i == 0) f = (b & d) | (~d & c);
+			else if (i == 1) f = (~c | b) ^ d;
+			else if (i == 2) f = (~b & d) | (b & c);
+			else if (i == 3) f = (b ^ c ^ d);
+
+			if (i == 0) f2 = (b2 ^ c2 ^ d2);
+			else if (i == 1) f2 = (~b2 & d2) | (b2 & c2);
+			else if (i == 2) f2 = (~c2 | b2) ^ d2;
+			else if (i == 3) f2 = (b2 & d2) | (~d2 & c2);
+
+			f = dataLongs[g] + a + T[i] + f;
+			s = r[0x0F + (((i & 1) ^ 1) << 4) - j];
+			f = (f << s) | (f >> (32 - s));
+
+			f2 = dataLongs[g2] + a2 + T2[i] + f2;
+			s2 = r[((i & 1) << 4) + j];
+			f2 = (f2 << s2) | (f2 >> (32 - s2));
+
+			a = d;
+			d = (c << 10) | (c >> 22);
+			c = b;
+			b = f;
+
+			a2 = d2;
+			d2 = (c2 << 10) | (c2 >> 22);
+			c2 = b2;
+			b2 = f2;
+		}
+	}
+
+	result[0] = h[3] + b + a2;
+	result[1] = h[2] + c + b2;
+	result[2] = h[1] + d + c2;
+	result[3] = h[0] + a + d2;
+
+	for (i = 0; i < 4; i++)
+	{
+		hash[4 * i + 0] = result[i] >> 0;
+		hash[4 * i + 1] = result[i] >> 8;
+		hash[4 * i + 2] = result[i] >> 16;
+		hash[4 * i + 3] = result[i] >> 24;
+	}
+}
+
+static const uint8_t table04[] =
+{
+	0x02, 0x03, 0x07, 0x0B, 0x0D, 0x08, 0x00, 0x01, 0x2B, 0x2D, 0x28, 0x20, 0x21, 0x0A, 0x0C, 0x0E,
+	0x22, 0x36, 0x23, 0x27, 0x29, 0x24, 0x25, 0x26, 0x2A, 0x3C, 0x3E, 0x3F, 0x0F, 0x2C, 0x2E, 0x2F,
+	0x12, 0x13, 0x17, 0x1B, 0x1C, 0x18, 0x10, 0x11, 0x19, 0x14, 0x15, 0x16, 0x1A, 0x09, 0x04, 0x05,
+	0x32, 0x33, 0x37, 0x3B, 0x06, 0x1C, 0x1E, 0x1F, 0x3D, 0x38, 0x30, 0x31, 0x39, 0x34, 0x35, 0x3A
+};
+
+static const uint8_t table05[] =
+{
+	0x08, 0x09, 0x0A, 0x03, 0x04, 0x3F, 0x27, 0x28, 0x29, 0x2A, 0x05, 0x0B, 0x1B, 0x1C, 0x1C, 0x1E,
+	0x20, 0x0C, 0x0D, 0x22, 0x23, 0x24, 0x00, 0x01, 0x02, 0x06, 0x07, 0x25, 0x26, 0x0E, 0x0F, 0x21,
+	0x10, 0x11, 0x12, 0x2E, 0x2F, 0x13, 0x14, 0x15, 0x2B, 0x2C, 0x2D, 0x16, 0x17, 0x18, 0x19, 0x1A,
+	0x30, 0x31, 0x37, 0x3B, 0x3C, 0x3D, 0x3E, 0x1F, 0x38, 0x39, 0x32, 0x33, 0x34, 0x35, 0x36, 0x3A
+};
+
+static const uint8_t table06[] =
+{
+	0x00, 0x01, 0x02, 0x06, 0x07, 0x08, 0x03, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x04, 0x05, 0x09, 0x0D,
+	0x20, 0x21, 0x22, 0x26, 0x27, 0x3A, 0x3B, 0x3C, 0x3E, 0x3F, 0x10, 0x11, 0x12, 0x16, 0x17, 0x28,
+	0x18, 0x13, 0x14, 0x15, 0x19, 0x1C, 0x1A, 0x1B, 0x1C, 0x1E, 0x1F, 0x23, 0x24, 0x25, 0x29, 0x2D,
+	0x30, 0x31, 0x32, 0x36, 0x37, 0x38, 0x33, 0x34, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0x35, 0x39, 0x3D
+};
+
+static const uint8_t table07[] =
+{
+	0x10, 0x11, 0x12, 0x17, 0x1C, 0x1E, 0x0E, 0x38, 0x39, 0x3A, 0x13, 0x14, 0x29, 0x2A, 0x16, 0x1F,
+	0x00, 0x01, 0x02, 0x3C, 0x3D, 0x3E, 0x3F, 0x07, 0x08, 0x09, 0x03, 0x04, 0x05, 0x06, 0x3B, 0x0A,
+	0x20, 0x21, 0x22, 0x19, 0x1A, 0x1B, 0x1C, 0x0B, 0x0C, 0x15, 0x23, 0x24, 0x25, 0x26, 0x18, 0x0F,
+	0x30, 0x31, 0x2B, 0x33, 0x34, 0x35, 0x36, 0x37, 0x27, 0x28, 0x2C, 0x2D, 0x2E, 0x2F, 0x32, 0x0D
+};
+
+static const uint8_t table08[] =
+{
+	0x10, 0x11, 0x1E, 0x17, 0x18, 0x19, 0x12, 0x13, 0x14, 0x1C, 0x1C, 0x15, 0x0D, 0x05, 0x06, 0x0A,
+	0x00, 0x01, 0x0E, 0x07, 0x08, 0x09, 0x02, 0x2D,	0x25, 0x26, 0x2A, 0x2B, 0x2F, 0x03, 0x04, 0x0C,
+	0x20, 0x21, 0x2E, 0x27, 0x28, 0x29, 0x30, 0x31,	0x3E, 0x37, 0x38, 0x39, 0x22, 0x23, 0x24, 0x2C,
+	0x32, 0x33, 0x34, 0x3C, 0x3D, 0x35, 0x36, 0x3A, 0x3B, 0x0B, 0x0F, 0x16, 0x1A, 0x1B, 0x1F, 0x3F
+};
+
+static const uint8_t table09[] =
+{
+	0x20, 0x21, 0x24, 0x22, 0x23, 0x2A, 0x2B, 0x33, 0x35, 0x38, 0x39, 0x36, 0x2D, 0x2C, 0x2E, 0x2F,
+	0x00, 0x01, 0x04, 0x02, 0x25, 0x28, 0x08, 0x09, 0x06, 0x07, 0x0A, 0x0B, 0x0D, 0x0C, 0x0E, 0x0F,
+	0x10, 0x11, 0x14, 0x12, 0x13, 0x15, 0x19, 0x16, 0x29, 0x26, 0x03, 0x17, 0x1A, 0x1C, 0x1C, 0x1E,
+	0x30, 0x31, 0x34, 0x32, 0x37, 0x3A, 0x3B, 0x3D, 0x3C, 0x3E, 0x3F, 0x1B, 0x05, 0x18, 0x27, 0x1F
+};
+
+static const uint8_t table0A[] =
+{
+	0x00, 0x04, 0x05, 0x0B, 0x0C, 0x06, 0x09, 0x0A, 0x0E, 0x0D, 0x0F, 0x25, 0x15, 0x1B, 0x1C, 0x16,
+	0x10, 0x11, 0x01, 0x02, 0x03, 0x07, 0x08, 0x12, 0x13, 0x17, 0x18, 0x14, 0x23, 0x27, 0x28, 0x24,
+	0x30, 0x31, 0x32, 0x33, 0x37, 0x38, 0x34, 0x35, 0x3B, 0x3C, 0x20, 0x21, 0x22, 0x2B, 0x2C, 0x26,
+	0x36, 0x39, 0x3A, 0x3E, 0x3D, 0x19, 0x1A, 0x1E, 0x1C, 0x1F, 0x3F, 0x29, 0x2A, 0x2E, 0x2D, 0x2F
+};
+
+static void hash_modes_04_to_0A_tables(uint8_t *data, uint8_t *hash, const uint8_t *table)
+{
+	int i;
+
+	for (i = 0; i < 16; i++)
+	{
+		hash[i] = table[i];
+		hash[i] ^= data[table[i]];
+		hash[i] ^= table[16 + i];
+		hash[i] ^= data[table[16 + i]];
+		hash[i] ^= table[32 + i];
+		hash[i] ^= data[table[32 + i]];
+		hash[i] ^= table[48 + i];
+		hash[i] ^= data[table[48 + i]];
+	}
+}
+
+static const uint8_t table0F[] = { 0xC7, 0x45, 0x15, 0x71, 0x61, 0x07, 0x05, 0x47 };
+static const uint8_t table10[] = { 0x0F, 0x47, 0x2B, 0x6C, 0xAD, 0x0F, 0xB3, 0xEA };
+static const uint8_t table11[] = { 0xB1, 0x46, 0xD1, 0x66, 0x5D, 0x28, 0x59, 0xD2 };
+static const uint8_t table12[] = { 0x0B, 0x4B, 0xD7, 0x68, 0x5F, 0xAD, 0x4B, 0xBB };
+static const uint8_t table13[] = { 0x4F, 0x4E, 0xE1, 0x6A, 0x21, 0xD3, 0xF7, 0xA6 };
+static const uint8_t table14[] = { 0xDD, 0x39, 0xB9, 0x65, 0x03, 0x91, 0xF1, 0xAC };
+static const uint8_t table15[] = { 0x3F, 0x50, 0xB5, 0x6F, 0x37, 0xC9, 0x13, 0x5D };
+static const uint8_t table16[] = { 0xF9, 0x5C, 0xFD, 0x72, 0x19, 0x42, 0x23, 0x6B };
+static const uint8_t table17[] = { 0xDF, 0x60, 0x93, 0x64, 0x33, 0x16, 0xB3, 0x8A };
+static const uint8_t table18[] = { 0x09, 0x64, 0x5F, 0x6B, 0xFB, 0x21, 0x19, 0xE4 };
+
+static void hash_modes_0F_to_18_tables(uint8_t *data, uint8_t *hash, const uint8_t *table)
+{
+	int i;
+	uint32_t t[4], tmp;
+
+	memset(hash, 0x00, 16);
+
+	t[0] = (table[1] << 8) + table[0];
+	t[1] = (table[3] << 8) + table[2];
+	t[2] = (table[5] << 8) + table[4];
+	t[3] = (table[7] << 8) + table[6];
+
+	for (i = 0; i < 60; i += 4)
+	{
+		t[0] = ((t[0] & 0xFFFF) * t[2]) + (t[0] >> 16);
+		t[1] = ((t[1] & 0xFFFF) * t[3]) + (t[1] >> 16);
+		tmp = t[0] + t[1];
+
+		hash[(i + 0) & 0x0F] = hash[(i + 0) & 0x0F] ^ data[i + 0] ^ (tmp >> 24);
+		hash[(i + 1) & 0x0F] = hash[(i + 1) & 0x0F] ^ data[i + 1] ^ (tmp >> 16);
+		hash[(i + 2) & 0x0F] = hash[(i + 2) & 0x0F] ^ data[i + 2] ^ (tmp >> 8);
+		hash[(i + 3) & 0x0F] = hash[(i + 3) & 0x0F] ^ data[i + 3] ^ (tmp >> 0);
+	}
+}
+
+static const uint8_t table19[] = { 0x02, 0x03, 0x05, 0x10 };
+static const uint8_t table1A[] = { 0x01, 0x05, 0x08, 0x10 };
+static const uint8_t table1B[] = { 0x03, 0x07, 0x08, 0x10 };
+static const uint8_t table1C[] = { 0x03, 0x05, 0x0A, 0x10 };
+static const uint8_t table1D[] = { 0x03, 0x07, 0x0A, 0x10 };
+static const uint8_t table1E[] = { 0x01, 0x05, 0x0B, 0x10 };
+static const uint8_t table1F[] = { 0x06, 0x07, 0x0B, 0x10 };
+static const uint8_t table20[] = { 0x01, 0x08, 0x0B, 0x10 };
+static const uint8_t table21[] = { 0x01, 0x07, 0x0C, 0x10 };
+static const uint8_t table22[] = { 0x05, 0x0B, 0x0C, 0x10 };
+static const uint8_t table23[] = { 0x0B, 0x0C, 0x0D, 0x10 };
+static const uint8_t table24[] = { 0x07, 0x09, 0x0E, 0x10 };
+static const uint8_t table25[] = { 0x01, 0x04, 0x0F, 0x10 };
+static const uint8_t table26[] = { 0x07, 0x08, 0x0F, 0x10 };
+static const uint8_t table27[] = { 0x02, 0x0B, 0x0F, 0x10 };
+
+static void hash_modes_19_to_27_tables_3(uint8_t *data, uint8_t *hash, const uint8_t *table)
+{
+	int i;
+	uint8_t val, it[4];
+	uint16_t seed = 0xFFFF, tmp;
+
+	memset(hash, 0x00, 16);
+
+	for (i = 0; i < 4; i++)
+	{
+		it[i] = 0x10 - table[i];
+	}
+
+	for (i = 0; i < 16; i++)
+	{
+		val = ((seed >> it[0]) ^ (seed >> it[1]) ^ (seed >> it[2]) ^ (seed >> it[3])) & 0x01;
+
+		if (val == 0x00)
+		{
+			seed = seed >> 1;
+		}
+		else
+		{
+			seed = (seed >> 1) | 0x8000;
+		}
+		tmp = seed + (data[i] << 8) + data[i + 32];
+
+		val = ((seed >> it[0]) ^ (seed >> it[1]) ^ (seed >> it[2]) ^ (seed >> it[3])) & 0x01;
+
+		if (val == 0x00)
+		{
+			seed = seed >> 1;
+		}
+		else
+		{
+			seed = (seed >> 1) | 0x8000;
+		}
+		tmp = tmp + seed + (data[i + 16] << 8) + data[i + 48];
+
+		hash[i & 0x0F] ^= tmp >> 8;
+		hash[(i + 1) & 0x0F] ^= tmp;
+	}
+}
+
+static void create_hash(uint8_t *data, int len, uint8_t *hash, int mode)
+{
+	if ((mode > 0x27) || (mode == 0x0B) || (mode == 0x0C) ||
+		(mode == 0x0D) || (mode == 0x0E) || (mode == 0))
+	{
+		memset(hash, 0, 16);
+		return;
+	}
+
+	uint8_t dataPadded[64];
+
+	pad_data(data, len, dataPadded);
+
+	switch (mode)
+	{
+		case 1:
+			hash_mode_01_custom_md5(dataPadded, hash);
+			break;
+
+		case 2:
+			hash_mode_02(dataPadded, hash);
+			break;
+
+		case 3:
+			hash_mode_03(dataPadded, hash);
+			break;
+
+		case 4:
+			hash_modes_04_to_0A_tables(dataPadded, hash, table04);
+			break;
+
+		case 5:
+			hash_modes_04_to_0A_tables(dataPadded, hash, table05);
+			break;
+
+		case 6:
+			hash_modes_04_to_0A_tables(dataPadded, hash, table06);
+			break;
+
+		case 7:
+			hash_modes_04_to_0A_tables(dataPadded, hash, table07);
+			break;
+
+		case 8:
+			hash_modes_04_to_0A_tables(dataPadded, hash, table08);
+			break;
+
+		case 9:
+			hash_modes_04_to_0A_tables(dataPadded, hash, table09);
+			break;
+
+		case 10:
+			hash_modes_04_to_0A_tables(dataPadded, hash, table0A);
+			break;
+
+		case 15:
+			hash_modes_0F_to_18_tables(dataPadded, hash, table0F);
+			break;
+
+		case 16:
+			hash_modes_0F_to_18_tables(dataPadded, hash, table10);
+			break;
+
+		case 17:
+			hash_modes_0F_to_18_tables(dataPadded, hash, table11);
+			break;
+
+		case 18:
+			hash_modes_0F_to_18_tables(dataPadded, hash, table12);
+			break;
+
+		case 19:
+			hash_modes_0F_to_18_tables(dataPadded, hash, table13);
+			break;
+
+		case 20:
+			hash_modes_0F_to_18_tables(dataPadded, hash, table14);
+			break;
+
+		case 21:
+			hash_modes_0F_to_18_tables(dataPadded, hash, table15);
+			break;
+
+		case 22:
+			hash_modes_0F_to_18_tables(dataPadded, hash, table16);
+			break;
+
+		case 23:
+			hash_modes_0F_to_18_tables(dataPadded, hash, table17);
+			break;
+
+		case 24:
+			hash_modes_0F_to_18_tables(dataPadded, hash, table18);
+			break;
+
+		case 25:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table19);
+			break;
+
+		case 26:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table1A);
+			break;
+
+		case 27:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table1B);
+			break;
+
+		case 28:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table1C);
+			break;
+
+		case 29:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table1D);
+			break;
+
+		case 30:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table1E);
+			break;
+
+		case 31:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table1F);
+			break;
+
+		case 32:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table20);
+			break;
+
+		case 33:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table21);
+			break;
+
+		case 34:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table22);
+			break;
+
+		case 35:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table23);
+			break;
+
+		case 36:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table24);
+			break;
+
+		case 37:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table25);
+			break;
+
+		case 38:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table26);
+			break;
+
+		case 39:
+			hash_modes_19_to_27_tables_3(dataPadded, hash, table27);
+			break;
+
+		default:
+			cs_log("A new hash mode [%d] is in use.", mode);
+			break;
+	}
+}
+
+static void create_hash_mode_03(uint8_t *data, uint8_t *hash)
+{
+	int i, j, c;
+	uint8_t buffer0[16], buffer1[8], buffer2[8], tmpBuff1[4], tmpBuff2[4];
+
+	uint8_t table[] =
+	{
+		0x68, 0xCE, 0xE7, 0x71, 0xCC, 0x3A, 0x0B, 0x6E, 0x2A, 0x43, 0x17, 0x07, 0x5A, 0xD9, 0x14, 0x5B,
+		0xB0, 0x8E, 0xA8, 0x7F, 0xD8, 0xA2, 0xCF, 0x73, 0xC2, 0xB9, 0x5D, 0x46, 0xDD, 0x2C, 0xE2, 0x2D,
+		0xFD, 0x50, 0xE9, 0x7C, 0x28, 0x72, 0x9B, 0xAA, 0xEC, 0x24, 0x74, 0xAB, 0x00, 0x1C, 0x8B, 0x65,
+		0x38, 0x13, 0x22, 0x82, 0xAC, 0x9A, 0x4D, 0x2B, 0xEA, 0x04, 0x31, 0x84, 0x32, 0x3D, 0x36, 0x53,
+		0x5F, 0x42, 0x96, 0xDE, 0x47, 0x08, 0x51, 0x4B, 0x3E, 0xD1, 0x1E, 0x12, 0xD2, 0x1F, 0x7D, 0x26,
+		0xCD, 0x57, 0x8C, 0xB6, 0xD3, 0xF8, 0x11, 0xAD, 0x6A, 0x88, 0x95, 0x21, 0xE8, 0xBF, 0x6B, 0x27,
+		0xBE, 0xA3, 0x33, 0xB8, 0x9E, 0xB3, 0x6C, 0xC3, 0x06, 0xC7, 0x6F, 0x99, 0x97, 0xDA, 0x09, 0xAF,
+		0xAE, 0xCB, 0x79, 0x37, 0x55, 0x85, 0x8D, 0x2F, 0x8A, 0x70, 0xA1, 0x7A, 0x66, 0x29, 0x67, 0x0F,
+		0xEB, 0x9C, 0xC8, 0xC4, 0xD6, 0x4C, 0xDF, 0x1A, 0xC0, 0x01, 0x64, 0xBC, 0x4E, 0xE1, 0x54, 0xD7,
+		0x4F, 0xB7, 0x5E, 0xCA, 0xF0, 0x91, 0xE4, 0x59, 0x4A, 0xC6, 0x83, 0x8F, 0xBD, 0x61, 0xFF, 0x56,
+		0x92, 0xF1, 0x5C, 0x77, 0xC9, 0x20, 0xF4, 0xE5, 0x10, 0x69, 0x03, 0x1D, 0xD5, 0x45, 0xF6, 0x0E,
+		0xEF, 0xA0, 0xE3, 0x58, 0xFC, 0xED, 0x80, 0x16, 0xEE, 0xFA, 0x02, 0xF5, 0xB4, 0x0A, 0xE0, 0x0C,
+		0xF7, 0xF9, 0xBA, 0x7E, 0x18, 0x78, 0x19, 0xB5, 0x0D, 0x44, 0x34, 0xD4, 0xDC, 0x30, 0x6D, 0x3B,
+		0x63, 0x41, 0x48, 0x40, 0xA7, 0xA5, 0xC5, 0x98, 0x76, 0x3F, 0xC1, 0x25, 0x93, 0x49, 0xD0, 0x62,
+		0x2E, 0x75, 0xDB, 0x94, 0xF3, 0x52, 0x05, 0x81, 0xFB, 0xBB, 0xA6, 0x89, 0x39, 0xA4, 0xF2, 0xA9,
+		0xFE, 0x60, 0x3C, 0x15, 0xB1, 0x35, 0x86, 0x9D, 0x9F, 0x90, 0x1B, 0xE6, 0x7B, 0x23, 0x87, 0xB2
+	};
+
+	for (i = 0; i < 4; i++)
+	{
+		buffer0[0 + i] = data[12 + i];
+		buffer0[4 + i] = data[8 + i];
+		buffer0[8 + i] = data[4 + i];
+		buffer0[12 + i] = data[0 + i];
+	}
+
+	for (c = 0; c < 12; c++)
+	{
+		for (i = 0; i < 4; i++)
+		{
+			buffer1[0 + i] = buffer0[8 + i] ^ buffer0[12 + i];
+			buffer1[4 + i] = buffer0[0 + i] ^ buffer0[4 + i];
+		}
+
+		for (i = 0; i < 8; i++)
+		{
+			buffer1[i] = table[buffer1[i] ^ data[16 + 16 * (c % 3) + i]];
+		}
+
+		for (j = 0; j < 8; j++)
+		{
+			buffer2[j] = 0;
+			for (i = 0; i < 8; i++)
+			{
+				buffer2[j] ^= buffer1[i] * (j * i + 1);
+			}
+		}
+
+		for (i = 0; i < 8; i++)
+		{
+			buffer2[i] = table[buffer2[i] ^ data[24 + 16 * (c % 3) + i]] ^ data[16 + 16 * (c % 3) + i];
+		}
+
+		for (i = 0; i < 4; i++)
+		{
+			buffer0[12 + i] ^= buffer2[0 + i];
+			buffer0[8 + i] ^= buffer2[0 + i];
+			buffer0[4 + i] ^= buffer2[4 + i];
+			buffer0[0 + i] ^= buffer2[4 + i];
+		}
+
+		tmpBuff1[0] = buffer0[14];
+		tmpBuff1[1] = buffer0[15];
+		tmpBuff1[2] = buffer0[12] ^ buffer0[14];
+		tmpBuff1[3] = buffer0[13] ^ buffer0[15];
+
+		tmpBuff2[0] = buffer0[6];
+		tmpBuff2[1] = buffer0[7];
+		tmpBuff2[2] = buffer0[4] ^ buffer0[6];
+		tmpBuff2[3] = buffer0[5] ^ buffer0[7];
+
+		for (i = 0; i < 4; i++)
+		{
+			buffer0[12 + i] = tmpBuff1[i];
+			buffer0[4 + i] = tmpBuff2[i];
+		}
+	}
+
+	for (i = 0; i < 4; i++)
+	{
+		hash[0 + i] = buffer0[12 + i] ^ data[0 + i];
+		hash[4 + i] = buffer0[8 + i] ^ data[4 + i];
+		hash[8 + i] = buffer0[4 + i] ^ data[8 + i];
+		hash[12 + i] = buffer0[0 + i] ^ data[12 + i];
+	}
+}
+
+static void create_data_cw_mode_03(uint8_t *seed, int lenSeed, uint8_t *basecw,
+									uint8_t val, uint8_t *ecmBody, uint8_t *data)
+{
+	int idxData = 8, idxSeed = 0, idxBase = 0;
+	uint8_t padding[] =
+	{
+		0x4A, 0x56, 0x7F, 0x16, 0xFC, 0x1F, 0x5B, 0x95,
+		0x19, 0xEF, 0x75, 0x14, 0x0E, 0x9E, 0x17, 0x3C,
+		0xF5, 0xB7, 0xA0, 0x93, 0xA3, 0x0F, 0xFA, 0x38,
+		0x7A, 0x34, 0x6C, 0xDC, 0xFB, 0xB0, 0x24, 0x42,
+		0x74, 0x72, 0x1C, 0xDC, 0x1E, 0xA1, 0x6D, 0xAB,
+		0xC8, 0x44, 0x53, 0xEF, 0x56, 0x00, 0xE9, 0x97,
+		0x48, 0x77, 0xF8, 0x00, 0x8E, 0x0B, 0x78, 0xA2
+	};
+
+	memcpy(data + 8, padding, 56);
+
+	data[0] = ecmBody[0x0F];
+	data[1] = ecmBody[0x09];
+	data[2] = ecmBody[0x10];
+	data[3] = ecmBody[0x11];
+	data[4] = ecmBody[0x05];
+	data[5] = ecmBody[0x07];
+	data[6] = ecmBody[0x08];
+	data[7] = ecmBody[0x0A];
+
+	while (idxBase < 7)
+	{
+		if ((idxBase == 0) || (idxBase == 2) || (idxBase == 5))
+		{
+			data[idxData++] = val;
+		}
+
+		if (idxSeed < lenSeed)
+		{
+			data[idxData++] = seed[idxSeed++];
+		}
+
+		data[idxData++] = basecw[idxBase++];
+	}
+}
+
+static void create_data_unmask_mode_03(uint8_t *ecmBody, uint8_t *data)
+{
+	uint8_t padding[] =
+	{
+		0xB1, 0x7C, 0xD2, 0xA7, 0x5E, 0x45, 0x6C, 0x36,
+		0xF0, 0xB6, 0x81, 0xF3, 0x25, 0x06, 0x65, 0x06,
+		0x6B, 0xBF, 0x4C, 0xE7, 0xED, 0x6E, 0x85, 0x00,
+		0xCC, 0xF2, 0x61, 0x48, 0x62, 0x24, 0x0E, 0x3C,
+		0x05, 0x89, 0xA5, 0x39, 0x5A, 0x4E, 0x9B, 0xC8,
+		0x14, 0x78, 0xEA, 0xB6, 0xFB, 0xF8, 0x10, 0xE6,
+		0x61, 0xF5, 0x3A, 0xBC, 0x5B, 0x79, 0x09, 0x97
+	};
+
+	memcpy(data + 8, padding, 56);
+
+	data[0] = ecmBody[0x17];
+	data[1] = ecmBody[0x26];
+	data[2] = ecmBody[0x19];
+	data[3] = ecmBody[0x21];
+	data[4] = ecmBody[0x26];
+	data[5] = ecmBody[0x31];
+	data[6] = ecmBody[0x21];
+	data[7] = ecmBody[0x27];
+}
+
+static void hash_04_add(uint32_t *buffer, int a, int b, int c, int d, int e, int f)
+{
+	uint32_t tmp1 = (buffer[a] & 1) + (buffer[b] & 1);
+	uint32_t tmp2 = (buffer[a] >> 1) + (buffer[b] >> 1) + (tmp1 >> 1);
+
+	buffer[e] = buffer[c] + buffer[d] + (tmp2 >> 31);
+	buffer[f] = tmp2 + tmp2 + (tmp1 & 1);
+}
+
+static void hash_04_shift(uint32_t *buffer, int a, int b, uint8_t shift)
+{
+	uint32_t tmp1 = (buffer[a] >> (32 - shift)) + (buffer[b] << shift);
+	uint32_t tmp2 = (buffer[b] >> (32 - shift)) + (buffer[a] << shift);
+
+	buffer[b] = tmp1;
+	buffer[a] = tmp2;
+}
+
+static void hash_04_xor(uint32_t *buffer, int a, int b, int c, int d)
+{
+	buffer[a] ^= buffer[b];
+	buffer[c] ^= buffer[d];
+}
+
+static void hash_04_swap(uint32_t *buffer, int a, int b)
+{
+	uint32_t tmp = buffer[a];
+
+	buffer[a] = buffer[b];
+	buffer[b] = tmp;
+}
+
+static void hash_04_core(uint32_t *buffer)
+{
+	hash_04_add(buffer, 0, 6, 7, 1, 7, 6);
+	hash_04_shift(buffer, 5, 4, 0x0D);
+	hash_04_xor(buffer, 4, 2, 5, 3);
+	hash_04_swap(buffer, 7, 6);
+	hash_04_add(buffer, 6, 2, 3, 7, 3, 2);
+	hash_04_shift(buffer, 1, 0, 0x10);
+	hash_04_xor(buffer, 0, 4, 1, 5);
+	hash_04_add(buffer, 6, 2, 3, 7, 7, 6);
+	hash_04_shift(buffer, 1, 0, 0x15);
+	hash_04_add(buffer, 6, 0, 1, 7, 1, 0);
+	hash_04_xor(buffer, 2, 4, 3, 5);
+	hash_04_shift(buffer, 5, 4, 0x11);
+	hash_04_xor(buffer, 4, 2, 5, 3);
+	hash_04_swap(buffer, 3, 2);
+}
+
+static void create_hash_mode_04(uint8_t *data, uint8_t *hash)
+{
+	int i, j;
+	uint32_t d0, d1, h0, h1, h2, h3;
+	uint32_t buffer[] =
+	{
+		0x1F253724, 0x3E8136B3, 0x9677CEDF, 0x25B5E75A,
+		0x9494BC16, 0xCFD3FB34, 0xF37C75BB, 0x97D4632E
+	};
+
+	for (j = 0; j < 64; j += 8)
+	{
+		d0 = (data[j + 3] << 24) + (data[j + 2] << 16) + (data[j + 1] << 8) + data[j + 0];
+		d1 = (data[j + 7] << 24) + (data[j + 6] << 16) + (data[j + 5] << 8) + data[j + 4];
+
+		buffer[0] ^= d0;
+		buffer[1] ^= d1;
+
+		for (i = 0; i < 2; i++)
+		{
+			hash_04_core(buffer);
+		}
+
+		buffer[6] ^= d0;
+		buffer[7] ^= d1;
+	}
+
+	buffer[1] ^= 0x40000000;
+	buffer[0] ^= 0x00000000;
+
+	for (i = 0; i < 2; i++)
+	{
+		hash_04_core(buffer);
+	}
+
+	buffer[7] ^= 0x40000000;
+	buffer[6] ^= 0x00000000;
+	buffer[2] ^= 0xEE;
+
+	for (i = 0; i < 4; i++)
+	{
+		hash_04_core(buffer);
+	}
+
+	h0 = buffer[0] ^ buffer[2] ^ buffer[4] ^ buffer[6];
+	h1 = buffer[1] ^ buffer[3] ^ buffer[5] ^ buffer[7];
+
+	hash[0] = (uint8_t)  h0;
+	hash[1] = (uint8_t) (h0 >>  8);
+	hash[2] = (uint8_t) (h0 >> 16);
+	hash[3] = (uint8_t) (h0 >> 24);
+	hash[4] = (uint8_t)  h1;
+	hash[5] = (uint8_t) (h1 >>  8);
+	hash[6] = (uint8_t) (h1 >> 16);
+	hash[7] = (uint8_t) (h1 >> 24);
+
+	buffer[4] ^= 0xDD;
+
+	for (i = 0; i < 4; i++)
+	{
+		hash_04_core(buffer);
+	}
+
+	h2 = buffer[0] ^ buffer[2] ^ buffer[4] ^ buffer[6];
+	h3 = buffer[1] ^ buffer[3] ^ buffer[5] ^ buffer[7];
+
+	hash[8]  = (uint8_t)  h2;
+	hash[9]  = (uint8_t) (h2 >> 8);
+	hash[10] = (uint8_t) (h2 >> 16);
+	hash[11] = (uint8_t) (h2 >> 24);
+	hash[12] = (uint8_t)  h3;
+	hash[13] = (uint8_t) (h3 >> 8);
+	hash[14] = (uint8_t) (h3 >> 16);
+	hash[15] = (uint8_t) (h3 >> 24);
+}
+
+static void create_data_cw_mode_04(uint8_t *seed, int lenSeed, uint8_t *basecw,
+									uint8_t val, uint8_t *ecmBody, uint8_t *data)
+{
+	uint8_t padding[] =
+	{
+		0x18, 0xD6, 0x24, 0xA8, 0xDE, 0x14, 0xD8, 0x30,
+		0x3C, 0xB2, 0x24, 0x54, 0x17, 0x5A, 0x28, 0x61,
+		0xBC, 0xB9, 0x29, 0xAD, 0xA5, 0x13, 0xD4, 0x24,
+		0x6D, 0x61, 0x40, 0xC8, 0xFD, 0x27, 0xD7, 0xFF,
+		0x3E, 0x84, 0x50, 0xC2, 0x47, 0x4C, 0xD5, 0xC5,
+		0xF2, 0x79, 0xAD, 0x02, 0xC5, 0x05, 0x7B, 0xFD,
+		0x60, 0x4A, 0x16, 0xE5, 0xAA, 0x0E, 0x97, 0x1C
+	};
+
+	memcpy(data + 8, padding, 56);
+
+	data[0] = ecmBody[0x0E];
+	data[1] = ecmBody[0x0A];
+	data[2] = ecmBody[0x0C];
+	data[3] = ecmBody[0x04];
+	data[4] = ecmBody[0x10];
+	data[5] = ecmBody[0x08];
+	data[6] = ecmBody[0x05];
+	data[7] = ecmBody[0x0F];
+
+	int idxData = 8, idxSeed = 0, idxBase = 0;
+
+	while (idxBase < 7)
+	{
+		if ((idxBase == 0) || (idxBase == 1) || (idxBase == 2))
+		{
+			data[idxData++] = val;
+		}
+
+		if (idxSeed < lenSeed)
+		{
+			data[idxData++] = seed[idxSeed++];
+		}
+
+		data[idxData++] = basecw[idxBase++];
+	}
+}
+
+static void create_data_unmask_mode_04(uint8_t *ecmBody, uint8_t *data)
+{
+	uint8_t padding[] =
+	{
+		0x0E, 0x4A, 0x85, 0x85, 0xF9, 0xC0, 0xCC, 0x00,
+		0xBA, 0x9B, 0x98, 0x35, 0x4C, 0xD2, 0xC1, 0x6C,
+		0x87, 0x32, 0x9B, 0x82, 0x31, 0x5B, 0x1D, 0xB4,
+		0xB8, 0x98, 0x74, 0xFF, 0x31, 0x66, 0x08, 0x79,
+		0x47, 0xCE, 0x96, 0x4D, 0xE9, 0x52, 0xCF, 0x8F,
+		0xEC, 0x5C, 0x07, 0xBC, 0x09, 0xA2, 0x82, 0x78,
+		0x3D, 0xB9, 0xFF, 0x3F, 0x76, 0x72, 0x6F, 0x9C
+	};
+
+	memcpy(data + 8, padding, 56);
+
+	data[0] = ecmBody[0x17];
+	data[1] = ecmBody[0x2B];
+	data[2] = ecmBody[0x1D];
+	data[3] = ecmBody[0x2D];
+	data[4] = ecmBody[0x0B];
+	data[5] = ecmBody[0x06];
+	data[6] = ecmBody[0x2F];
+	data[7] = ecmBody[0x1E];
+}
+
+static uint8_t get_mode_cw(uint8_t *extraData)
+{
+	uint64_t data = ((uint32_t)extraData[0] << 24) + (extraData[1] << 16) + (extraData[2] << 8) + extraData[3];
+	uint64_t t1 = (data * 0x76E9DEA7) >> 50;
+	uint64_t t2 = (t1 * 0x51EB851F) >> 36;
+	uint64_t t3 = t2 * 0x32;
+	uint8_t r = t1 - t3;
+	return r;
+}
+
+static uint8_t get_mode_unmask(uint8_t *extraData)
+{
+	uint64_t data = ((uint32_t)extraData[0] << 24) + (extraData[1] << 16) + (extraData[2] << 8) + extraData[3];
+	uint64_t t1 = (data * 0xB9CD6BE5) >> 45;
+	uint64_t t2 = (t1 * 0x51EB851F) >> 36;
+	uint64_t t3 = t2 * 0x32;
+	uint8_t r = t1 - t3;
+	return r;
+}
+
+static void create_data_ecm_emm(uint8_t *emmEcm, uint8_t *pos, int lenHeader, int len, uint8_t *data)
+{
+	int i;
+
+	for (i = 0; i < len; i++)
+	{
+		data[i] = emmEcm[lenHeader + pos[i]];
+	}
+}
+
+static uint8_t create_data_cw(uint8_t *seed, uint8_t lenSeed, uint8_t *baseCw,
+								uint8_t val, uint8_t *seedEcmCw, uint8_t *data)
+{
+	int i;
+
+	for (i = 0; i < lenSeed; i++)
+	{
+		data[i] = seed[i];
+	}
+
+	for (i = 0; i < 7; i++)
+	{
+		data[lenSeed + i] = baseCw[i];
+	}
+
+	data[lenSeed + 7] = val;
+
+	for (i = 0; i < 16; i++)
+	{
+		data[lenSeed + 7 + 1 + i] = seedEcmCw[i];
+	}
+
+	return lenSeed + 7 + 1 + 0x10;
+}
+
+static uint8_t unmask_ecm(uint8_t *ecm, uint8_t *seedEcmCw, uint8_t *modeCW)
+{
+	int i, l;
+	uint8_t data[64], mask[16];
+	uint8_t hashModeEcm, hashModeCw, modeUnmask = 0;
+	uint32_t crc;
+
+	uint8_t sourcePos[] =
+	{
+		0x04, 0x05, 0x06, 0x07, 0x0A, 0x0B, 0x0C, 0x0D,
+		0x0E, 0x0F, 0x10, 0x17, 0x1C, 0x1D, 0x1F, 0x23,
+		0x24, 0x25, 0x26, 0x27, 0x29, 0x2C, 0x2D, 0x2E
+	};
+
+	uint8_t destPos[] =
+	{
+		0x08, 0x09, 0x11, 0x18, 0x19, 0x1A, 0x1B, 0x1E,
+		0x20, 0x21, 0x22, 0x28, 0x2A, 0x2B, 0x2F, 0x30
+	};
+
+	uint8_t seedCwPos[] = { 0x07, 0x0A, 0x04, 0x0D, 0x05, 0x0E, 0x06, 0x0B, 0x10, 0x0C, 0x0F };
+
+	// Create seed for CW decryption
+	memset(seedEcmCw, 0, 16);
+
+	int extraBytesLen = ecm[9];
+	int startOffset = extraBytesLen + 10;
+
+	for (i = 0; i < 11; i++)
+	{
+		seedEcmCw[i] = ecm[startOffset + seedCwPos[i]];
+	}
+
+	*modeCW = 0;
+	if (extraBytesLen > 0)
+	{
+		*modeCW = get_mode_cw(ecm + 10);
+	}
+
+	// Read hash mode CW
+	hashModeCw = ecm[28 + extraBytesLen] ^ crc8_calc(seedEcmCw, 16);
+
+	// Create mask for ECM decryption
+	create_data_ecm_emm(ecm, sourcePos, startOffset, 24, data);
+
+	hashModeEcm = ecm[8] ^ crc8_calc(data, 24);
+
+	if (extraBytesLen > 0)
+	{
+		modeUnmask = get_mode_unmask(ecm + 10);
+	}
+
+	if (modeUnmask == 0x03)
+	{
+		ecm[startOffset + 0x21] -= ecm[startOffset + 0x07];
+		ecm[startOffset + 0x26] -= ecm[startOffset + 0x05];
+		ecm[startOffset + 0x26] -= ecm[startOffset + 0x08];
+		ecm[startOffset + 0x19] -= ecm[startOffset + 0x06];
+		ecm[startOffset + 0x31] -= ecm[startOffset + 0x09];
+		ecm[startOffset + 0x27] -= ecm[startOffset + 0x0C];
+		ecm[startOffset + 0x21] -= ecm[startOffset + 0x0B];
+		ecm[startOffset + 0x17] -= ecm[startOffset + 0x04];
+
+		create_data_unmask_mode_03(ecm + startOffset, data);
+		create_hash_mode_03(data, mask);
+
+		// Unmask body
+		ecm[startOffset + 0x06] ^= mask[0x02];
+		ecm[startOffset + 0x0B] ^= mask[0x06];
+		ecm[startOffset + 0x0C] ^= mask[0x07];
+		ecm[startOffset + 0x0D] ^= mask[0x08];
+		ecm[startOffset + 0x0E] ^= mask[0x09];
+		ecm[startOffset + 0x0F] ^= mask[0x0A];
+		ecm[startOffset + 0x11] ^= mask[0x0B];
+		ecm[startOffset + 0x18] ^= mask[0x0C];
+		ecm[startOffset + 0x2D] ^= mask[0x0A];
+		ecm[startOffset + 0x07] ^= mask[0x03];
+		ecm[startOffset + 0x1B] ^= mask[0x0D];
+		ecm[startOffset + 0x30] ^= mask[0x0C];
+		ecm[startOffset + 0x1C] ^= mask[0x0E];
+		ecm[startOffset + 0x1E] ^= mask[0x00];
+		ecm[startOffset + 0x04] ^= mask[0x00];
+		ecm[startOffset + 0x05] ^= mask[0x01];
+		ecm[startOffset + 0x1F] ^= mask[0x01];
+		ecm[startOffset + 0x2C] ^= mask[0x09];
+		ecm[startOffset + 0x20] ^= mask[0x02];
+		ecm[startOffset + 0x1D] ^= mask[0x0F];
+		ecm[startOffset + 0x23] ^= mask[0x04];
+		ecm[startOffset + 0x09] ^= mask[0x05];
+		ecm[startOffset + 0x22] ^= mask[0x03];
+		ecm[startOffset + 0x24] ^= mask[0x05];
+		ecm[startOffset + 0x08] ^= mask[0x04];
+		ecm[startOffset + 0x28] ^= mask[0x06];
+		ecm[startOffset + 0x29] ^= mask[0x07];
+		ecm[startOffset + 0x2A] ^= mask[0x08];
+		ecm[startOffset + 0x2E] ^= mask[0x0B];
+
+		for (i = 0; i < ecm[9]; i++)
+		{
+			ecm[10 + i] = 0x00;
+		}
+	}
+	else if (modeUnmask == 0x04)
+	{
+		ecm[startOffset + 0x1E] -= ecm[startOffset + 0x0D];
+		ecm[startOffset + 0x1D] -= ecm[startOffset + 0x07];
+		ecm[startOffset + 0x2B] -= ecm[startOffset + 0x05];
+		ecm[startOffset + 0x2D] -= ecm[startOffset + 0x08];
+		ecm[startOffset + 0x17] -= ecm[startOffset + 0x04];
+		ecm[startOffset + 0x2F] -= ecm[startOffset + 0x0C];
+		ecm[startOffset + 0x06] -= ecm[startOffset + 0x0A];
+		ecm[startOffset + 0x0B] -= ecm[startOffset + 0x09];
+
+		create_data_unmask_mode_04(ecm + startOffset, data);
+		create_hash_mode_04(data, mask);
+
+		// Unmask body
+		ecm[startOffset + 0x04] ^= mask[0x00];
+		ecm[startOffset + 0x05] ^= mask[0x01];
+		ecm[startOffset + 0x07] ^= mask[0x02];
+		ecm[startOffset + 0x08] ^= mask[0x03];
+		ecm[startOffset + 0x09] ^= mask[0x04];
+		ecm[startOffset + 0x0A] ^= mask[0x05];
+		ecm[startOffset + 0x0C] ^= mask[0x06];
+		ecm[startOffset + 0x0D] ^= mask[0x07];
+		ecm[startOffset + 0x0E] ^= mask[0x08];
+		ecm[startOffset + 0x10] ^= mask[0x09];
+		ecm[startOffset + 0x11] ^= mask[0x0A];
+		ecm[startOffset + 0x18] ^= mask[0x0B];
+		ecm[startOffset + 0x1A] ^= mask[0x0C];
+		ecm[startOffset + 0x1B] ^= mask[0x0D];
+		ecm[startOffset + 0x1C] ^= mask[0x0E];
+		ecm[startOffset + 0x1F] ^= mask[0x0F];
+		ecm[startOffset + 0x22] ^= mask[0x00];
+		ecm[startOffset + 0x24] ^= mask[0x01];
+		ecm[startOffset + 0x25] ^= mask[0x02];
+		ecm[startOffset + 0x26] ^= mask[0x03];
+		ecm[startOffset + 0x27] ^= mask[0x04];
+		ecm[startOffset + 0x28] ^= mask[0x05];
+		ecm[startOffset + 0x29] ^= mask[0x06];
+		ecm[startOffset + 0x2A] ^= mask[0x07];
+		ecm[startOffset + 0x2C] ^= mask[0x08];
+		ecm[startOffset + 0x2E] ^= mask[0x09];
+		ecm[startOffset + 0x31] ^= mask[0x0A];
+
+		for (i = 0; i < ecm[9]; i++)
+		{
+			ecm[10 + i] = 0x00;
+		}
+	}
+	else
+	{
+		create_hash(data, 24, mask, hashModeEcm);
+
+		// Unmask body
+		for (i = 0; i < 16; i++)
+		{
+			ecm[startOffset + destPos[i]] ^= mask[i & 0x0F];
+		}
+	}
+
+	// Fix header
+	ecm[3] &= 0x0F;
+	ecm[3] |= 0x30;
+	ecm[8] = 0x00;
+	ecm[28 + extraBytesLen] = 0x00;
+
+	// Fix CRC (optional)
+	l = (((ecm[1] << 8) + ecm[2]) & 0xFFF) + 3 - 4;
+
+	crc = ccitt32_crc(ecm, l);
+
+	ecm[l + 0] = crc >> 24;
+	ecm[l + 1] = crc >> 16;
+	ecm[l + 2] = crc >> 8;
+	ecm[l + 3] = crc >> 0;
+
+	for (i = 0; i < 11; i++)
+	{
+		seedEcmCw[i] = ecm[startOffset + seedCwPos[i]];
+	}
+
+	return hashModeCw;
+}
+
+static void create_cw(uint8_t *seed, uint8_t lenSeed, uint8_t *baseCw, uint8_t val, uint8_t *seedEcmCw,
+						uint8_t *cw, int modeDesCsa, int hashMode, int modeCW, uint8_t *ecmBody)
+{
+	int i;
+	uint8_t data[64], hash[16], lenData;
+	uint8_t tableFixParity[] =
+	{
+		0x01, 0x01, 0x02, 0x02, 0x04, 0x04, 0x07, 0x07,
+		0x08, 0x08, 0x0B, 0x0B, 0x0D, 0x0D, 0x0E, 0x0E,
+		0x10, 0x10, 0x13, 0x13, 0x15, 0x15, 0x16, 0x16,
+		0x19, 0x19, 0x1A, 0x1A, 0x1C, 0x1C, 0x1F, 0x1F,
+		0x20, 0x20, 0x23, 0x23, 0x25, 0x25, 0x26, 0x26,
+		0x29, 0x29, 0x2A, 0x2A, 0x2C, 0x2C, 0x2F, 0x2F,
+		0x31, 0x31, 0x32, 0x32, 0x34, 0x34, 0x37, 0x37,
+		0x38, 0x38, 0x3B, 0x3B, 0x3D, 0x3D, 0x3E, 0x3E,
+		0x40, 0x40, 0x43, 0x43, 0x45, 0x45, 0x46, 0x46,
+		0x49, 0x49, 0x4A, 0x4A, 0x4C, 0x4C, 0x4F, 0x4F,
+		0x51, 0x51, 0x52, 0x52, 0x54, 0x54, 0x57, 0x57,
+		0x58, 0x58, 0x5B, 0x5B, 0x5D, 0x5D, 0x5E, 0x5E,
+		0x61, 0x61, 0x62, 0x62, 0x64, 0x64, 0x67, 0x67,
+		0x68, 0x68, 0x6B, 0x6B, 0x6D, 0x6D, 0x6E, 0x6E,
+		0x70, 0x70, 0x73, 0x73, 0x75, 0x75, 0x76, 0x76,
+		0x79, 0x79, 0x7A, 0x7A, 0x7C, 0x7C, 0x7F, 0x7F,
+		0x80, 0x80, 0x83, 0x83, 0x85, 0x85, 0x86, 0x86,
+		0x89, 0x89, 0x8A, 0x8A, 0x8C, 0x8C, 0x8F, 0x8F,
+		0x91, 0x91, 0x92, 0x92, 0x94, 0x94, 0x97, 0x97,
+		0x98, 0x98, 0x9B, 0x9B, 0x9D, 0x9D, 0x9E, 0x9E,
+		0xA1, 0xA1, 0xA2, 0xA2, 0xA4, 0xA4, 0xA7, 0xA7,
+		0xA8, 0xA8, 0xAB, 0xAB, 0xAD, 0xAD, 0xAE, 0xAE,
+		0xB0, 0xB0, 0xB3, 0xB3, 0xB5, 0xB5, 0xB6, 0xB6,
+		0xB9, 0xB9, 0xBA, 0xBA, 0xBC, 0xBC, 0xBF, 0xBF,
+		0xC1, 0xC1, 0xC2, 0xC2, 0xC4, 0xC4, 0xC7, 0xC7,
+		0xC8, 0xC8, 0xCB, 0xCB, 0xCD, 0xCD, 0xCE, 0xCE,
+		0xD0, 0xD0, 0xD3, 0xD3, 0xD5, 0xD5, 0xD6, 0xD6,
+		0xD9, 0xD9, 0xDA, 0xDA, 0xDC, 0xDC, 0xDF, 0xDF,
+		0xE0, 0xE0, 0xE3, 0xE3, 0xE5, 0xE5, 0xE6, 0xE6,
+		0xE9, 0xE9, 0xEA, 0xEA, 0xEC, 0xEC, 0xEF, 0xEF,
+		0xF1, 0xF1, 0xF2, 0xF2, 0xF4, 0xF4, 0xF7, 0xF7,
+		0xF8, 0xF8, 0xFB, 0xFB, 0xFD, 0xFD, 0xFE, 0xFE
+	};
+
+	if (modeCW == 0x03)
+	{
+		create_data_cw_mode_03(seed, lenSeed, baseCw, val, ecmBody, data);
+		create_hash_mode_03(data, hash);
+
+		cw[0] = hash[0x09];
+		cw[1] = hash[0x01];
+		cw[2] = hash[0x0F];
+		cw[3] = hash[0x0E];
+		cw[4] = hash[0x04];
+		cw[5] = hash[0x02];
+		cw[6] = hash[0x05];
+		cw[7] = hash[0x0D];
+	}
+	else if (modeCW == 0x04)
+	{
+		create_data_cw_mode_04(seed, lenSeed, baseCw, val, ecmBody, data);
+		create_hash_mode_04(data, hash);
+
+		cw[0] = hash[0x08];
+		cw[1] = hash[0x0F];
+		cw[2] = hash[0x02];
+		cw[3] = hash[0x0A];
+		cw[4] = hash[0x06];
+		cw[5] = hash[0x03];
+		cw[6] = hash[0x09];
+		cw[7] = hash[0x0D];
+	}
+	else
+	{
+		lenData = create_data_cw(seed, lenSeed, baseCw, val, seedEcmCw, data);
+		create_hash(data, lenData, hash, hashMode);
+
+		for (i = 0; i < 8; i++)
+		{
+			cw[i] = hash[i];
+		}
+	}
+
+	if (modeDesCsa == 0) // DES - Fix Parity Bits
+	{
+		for (i = 0; i < 8; i++)
+		{
+			cw[i] = tableFixParity[cw[i]];
+		}
+	}
+	else if (modeDesCsa == 1) // CSA - Fix Checksums
+	{
+		cw[3] = cw[0] + cw[1] + cw[2];
+		cw[7] = cw[4] + cw[5] + cw[6];
+	}
+}
+
+static uint32_t create_channel_hash(uint16_t caid, uint16_t tsid, uint16_t onid, uint32_t ens)
+{
+	uint8_t buffer[8];
+	uint32_t channel_hash = 0;
+
+	if (ens)
+	{
+		i2b_buf(2, tsid, buffer);
+		i2b_buf(2, onid, buffer + 2);
+		i2b_buf(4, ens, buffer + 4);
+
+		channel_hash = crc32(caid, buffer, sizeof(buffer));
+	}
+
+	return channel_hash;
+}
+
+static uint16_t get_channel_group(uint32_t channel_hash)
+{
+	uint8_t tmp[2];
+	uint16_t group = 0;
+
+	if (channel_hash && emu_find_key('P', channel_hash, 0x00000000, "GROUP", tmp, 2, 0, 0, 0, NULL))
+	{
+		group = b2i(2, tmp);
+	}
+
+	return group;
+}
+
+static inline int8_t get_ecm_key(uint8_t *key, uint32_t provider, uint32_t ignore_mask, uint8_t keyIndex, uint32_t keyRef)
+{
+	return emu_find_key('P', provider, ignore_mask, keyIndex == 1 ? "01" : "00", key, 7, 0, keyRef, 0, NULL);
+}
+
+static inline int8_t get_emm_key(uint8_t *key, char *uniqueAddress, uint32_t keyRef, uint32_t *groupId)
+{
+	return emu_find_key('P', 0, 0xFFFFFFFF, uniqueAddress, key, 7, 0, keyRef, 0, groupId);
+}
+
+static const uint8_t PowerVu_A0_S_1[16] =
+{
+	0x33, 0xA4, 0x44, 0x3C, 0xCA, 0x2E, 0x75, 0x7B,
+	0xBC, 0xE6, 0xE5, 0x35, 0xA0, 0x55, 0xC9, 0xA2
+};
+
+static const uint8_t PowerVu_A0_S_2[16] =
+{
+	0x5A, 0xB0, 0x2C, 0xBC, 0xDA, 0x32, 0xE6, 0x92,
+	0x40, 0x53, 0x6E, 0xF9, 0x69, 0x11, 0x1E, 0xFB
+};
+
+static const uint8_t PowerVu_A0_S_3[16] =
+{
+	0x4E, 0x18, 0x9B, 0x19, 0x79, 0xFB, 0x01, 0xFA,
+	0xE3, 0xE1, 0x28, 0x3D, 0x32, 0xE4, 0x92, 0xEA
+};
+
+static const uint8_t PowerVu_A0_S_4[16] =
+{
+	0x05, 0x6F, 0x37, 0x66, 0x35, 0xE1, 0x58, 0xD0,
+	0xB4, 0x6A, 0x97, 0xAE, 0xD8, 0x91, 0x27, 0x56
+};
+
+static const uint8_t PowerVu_A0_S_5[16] =
+{
+	0x7B, 0x26, 0xAD, 0x34, 0x3D, 0x77, 0x39, 0x51,
+	0xE0, 0xE0, 0x48, 0x8C, 0x39, 0xF5, 0xE8, 0x47
+};
+
+static const uint8_t PowerVu_A0_S_6[16] =
+{
+	0x74, 0xFA, 0x4D, 0x79, 0x42, 0x39, 0xD1, 0xA4,
+	0x99, 0xA3, 0x97, 0x07, 0xDF, 0x14, 0x3A, 0xC4
+};
+
+static const uint8_t PowerVu_A0_S_7[16] =
+{
+	0xC6, 0x1E, 0x3C, 0x24, 0x11, 0x08, 0x5D, 0x6A,
+	0xEB, 0x97, 0xB9, 0x25, 0xA7, 0xFA, 0xE9, 0x1A
+};
+
+static const uint8_t PowerVu_A0_S_8[16] =
+{
+	0x9A, 0xAD, 0x72, 0xD7, 0x7C, 0x68, 0x3B, 0x55,
+	0x1D, 0x4A, 0xA2, 0xB0, 0x38, 0xB9, 0x56, 0xD0
+};
+
+static const uint8_t PowerVu_A0_S_9[32] =
+{
+	0x61, 0xDA, 0x5F, 0xB7, 0xEB, 0xC6, 0x3F, 0x6C,
+	0x09, 0xF3, 0x64, 0x38, 0x33, 0x08, 0xAA, 0x15,
+	0xCC, 0xEF, 0x22, 0x64, 0x01, 0x2C, 0x12, 0xDE,
+	0xF4, 0x6E, 0x3C, 0xCD, 0x1A, 0x64, 0x63, 0x7C
+};
+
+static const uint8_t PowerVu_00_S_1[16] =
+{
+	0x97, 0x13, 0xEB, 0x6B, 0x04, 0x5E, 0x60, 0x3A,
+	0xD9, 0xCC, 0x91, 0xC2, 0x5A, 0xFD, 0xBA, 0x0C
+};
+
+static const uint8_t PowerVu_00_S_2[16] =
+{
+	0x61, 0x3C, 0x03, 0xB0, 0xB5, 0x6F, 0xF8, 0x01,
+	0xED, 0xE0, 0xE5, 0xF3, 0x78, 0x0F, 0x0A, 0x73
+};
+
+static const uint8_t PowerVu_00_S_3[16] =
+{
+	0xFD, 0xDF, 0xD2, 0x97, 0x06, 0x14, 0x91, 0xB5,
+	0x36, 0xAD, 0xBC, 0xE1, 0xB3, 0x00, 0x66, 0x41
+};
+
+static const uint8_t PowerVu_00_S_4[16] =
+{
+	0x8B, 0xD9, 0x18, 0x0A, 0xED, 0xEE, 0x61, 0x34,
+	0x1A, 0x79, 0x80, 0x8C, 0x1E, 0x7F, 0xC5, 0x9F
+};
+
+static const uint8_t PowerVu_00_S_5[16] =
+{
+	0xB0, 0xA1, 0xF2, 0xB8, 0xEA, 0x72, 0xDD, 0xD3,
+	0x30, 0x65, 0x2B, 0x1E, 0xE9, 0xE1, 0x45, 0x29
+};
+
+static const uint8_t PowerVu_00_S_6[16] =
+{
+	0x5D, 0xCA, 0x53, 0x75, 0xB2, 0x24, 0xCE, 0xAF,
+	0x21, 0x54, 0x9E, 0xBE, 0x02, 0xA9, 0x4C, 0x5D
+};
+
+static const uint8_t PowerVu_00_S_7[16] =
+{
+	0x42, 0x66, 0x72, 0x83, 0x1B, 0x2D, 0x22, 0xC9,
+	0xF8, 0x4D, 0xBA, 0xCD, 0xBB, 0x20, 0xBD, 0x6B
+};
+
+static const uint8_t PowerVu_00_S_8[16] =
+{
+	0xC4, 0x0C, 0x6B, 0xD3, 0x6D, 0x94, 0x7E, 0x53,
+	0xCE, 0x96, 0xAC, 0x40, 0x2C, 0x7A, 0xD3, 0xA9
+};
+
+static const uint8_t PowerVu_00_S_9[32] =
+{
+	0x31, 0x82, 0x4F, 0x9B, 0xCB, 0x6F, 0x9D, 0xB7,
+	0xAE, 0x68, 0x0B, 0xA0, 0x93, 0x15, 0x32, 0xE2,
+	0xED, 0xE9, 0x47, 0x29, 0xC2, 0xA8, 0x92, 0xEF,
+	0xBA, 0x27, 0x22, 0x57, 0x76, 0x54, 0xC0, 0x59
+};
+
+static uint8_t powervu_sbox(uint8_t *input, uint8_t mode)
+{
+	uint8_t s_index, bit, last_index, last_bit;
+	uint8_t const *Sbox1, *Sbox2, *Sbox3, *Sbox4, *Sbox5, *Sbox6, *Sbox7, *Sbox8, *Sbox9;
+
+	if (mode)
+	{
+		Sbox1 = PowerVu_A0_S_1;
+		Sbox2 = PowerVu_A0_S_2;
+		Sbox3 = PowerVu_A0_S_3;
+		Sbox4 = PowerVu_A0_S_4;
+		Sbox5 = PowerVu_A0_S_5;
+		Sbox6 = PowerVu_A0_S_6;
+		Sbox7 = PowerVu_A0_S_7;
+		Sbox8 = PowerVu_A0_S_8;
+		Sbox9 = PowerVu_A0_S_9;
+	}
+	else
+	{
+		Sbox1 = PowerVu_00_S_1;
+		Sbox2 = PowerVu_00_S_2;
+		Sbox3 = PowerVu_00_S_3;
+		Sbox4 = PowerVu_00_S_4;
+		Sbox5 = PowerVu_00_S_5;
+		Sbox6 = PowerVu_00_S_6;
+		Sbox7 = PowerVu_00_S_7;
+		Sbox8 = PowerVu_00_S_8;
+		Sbox9 = PowerVu_00_S_9;
+	}
+
+	bit = (get_bit(input[2], 0) << 2) | (get_bit(input[3], 4) << 1) | (get_bit(input[5], 3));
+	s_index = (get_bit(input[0], 0) << 3) | (get_bit(input[2], 6) << 2) | (get_bit(input[2], 4) << 1) | (get_bit(input[5], 7));
+	last_bit = get_bit(Sbox1[s_index], 7 - bit);
+
+	bit = (get_bit(input[5], 0) << 2) | (get_bit(input[4], 0) << 1) | (get_bit(input[6], 2));
+	s_index = (get_bit(input[2], 1) << 3) | (get_bit(input[2], 2) << 2) | (get_bit(input[5], 5) << 1) | (get_bit(input[5], 1));
+	last_bit = last_bit | (get_bit(Sbox2[s_index], 7 - bit) << 1);
+
+	bit = (get_bit(input[6], 0) << 2) | (get_bit(input[1], 7) << 1) | (get_bit(input[6], 7));
+	s_index = (get_bit(input[1], 3) << 3) | (get_bit(input[3], 7) << 2) | (get_bit(input[1], 5) << 1) | (get_bit(input[5], 2));
+	last_bit = last_bit | (get_bit(Sbox3[s_index], 7 - bit) << 2);
+
+	bit = (get_bit(input[1], 0) << 2) | (get_bit(input[2], 7) << 1) | (get_bit(input[2], 5));
+	s_index = (get_bit(input[6], 3) << 3) | (get_bit(input[6], 4) << 2) | (get_bit(input[6], 6) << 1) | (get_bit(input[3], 5));
+	last_index = get_bit(Sbox4[s_index], 7 - bit);
+
+	bit = (get_bit(input[3], 3) << 2) | (get_bit(input[4], 6) << 1) | (get_bit(input[3], 2));
+	s_index = (get_bit(input[3], 1) << 3) | (get_bit(input[4], 5) << 2) | (get_bit(input[3], 0) << 1) | (get_bit(input[4], 7));
+	last_index = last_index | (get_bit(Sbox5[s_index], 7 - bit) << 1);
+
+	bit = (get_bit(input[5], 4) << 2) | (get_bit(input[4], 4) << 1) | (get_bit(input[1], 2));
+	s_index = (get_bit(input[2], 3) << 3) | (get_bit(input[6], 5) << 2) | (get_bit(input[1], 4) << 1) | (get_bit(input[4], 1));
+	last_index = last_index | (get_bit(Sbox6[s_index], 7 - bit) << 2);
+
+	bit = (get_bit(input[0], 6) << 2) | (get_bit(input[0], 7) << 1) | (get_bit(input[0], 4));
+	s_index = (get_bit(input[0], 5) << 3) | (get_bit(input[0], 3) << 2) | (get_bit(input[0], 1) << 1) | (get_bit(input[0], 2));
+	last_index = last_index | (get_bit(Sbox7[s_index], 7 - bit) << 3);
+
+	bit = (get_bit(input[4], 2) << 2) | (get_bit(input[4], 3) << 1) | (get_bit(input[1], 1));
+	s_index = (get_bit(input[1], 6) << 3) | (get_bit(input[6], 1) << 2) | (get_bit(input[5], 6) << 1) | (get_bit(input[3], 6));
+	last_index = last_index | (get_bit(Sbox8[s_index], 7 - bit) << 4);
+
+	return (get_bit(Sbox9[last_index & 0x1F], 7 - last_bit) & 1) ? 1 : 0;
+}
+
+static void powervu_decrypt(uint8_t *data, uint32_t length, uint8_t *key, uint8_t sbox)
+{
+	uint32_t i;
+	int32_t j, k;
+	uint8_t curByte, tmpBit;
+
+	for (i = 0; i < length; i++)
+	{
+		curByte = data[i];
+
+		for (j = 7; j >= 0; j--)
+		{
+			data[i] = set_bit(data[i], j, (get_bit(curByte, j) ^ powervu_sbox(key, sbox)) ^ get_bit(key[0], 7));
+			tmpBit = get_bit(data[i], j) ^ (get_bit(key[6], 0));
+
+			if (tmpBit)
+			{
+				key[3] ^= 0x10;
+			}
+
+			for (k = 6; k > 0; k--)
+			{
+				key[k] = (key[k] >> 1) | (key[k - 1] << 7);
+			}
+
+			key[0] = (key[0] >> 1);
+			key[0] = set_bit(key[0], 7, tmpBit);
+		}
+	}
+}
+
+static void expand_des_key(unsigned char *key)
+{
+	uint8_t i, j, parity;
+	uint8_t tmpKey[7];
+
+	memcpy(tmpKey, key, 7);
+
+	key[0] = (tmpKey[0] & 0xFE);
+	key[1] = ((tmpKey[0] << 7) | ((tmpKey[1] >> 1) & 0xFE));
+	key[2] = ((tmpKey[1] << 6) | ((tmpKey[2] >> 2) & 0xFE));
+	key[3] = ((tmpKey[2] << 5) | ((tmpKey[3] >> 3) & 0xFE));
+	key[4] = ((tmpKey[3] << 4) | ((tmpKey[4] >> 4) & 0xFE));
+	key[5] = ((tmpKey[4] << 3) | ((tmpKey[5] >> 5) & 0xFE));
+	key[6] = ((tmpKey[5] << 2) | ((tmpKey[6] >> 6) & 0xFE));
+	key[7] = (tmpKey[6] << 1);
+
+	for (i = 0; i < 8; i++)
+	{
+		parity = 1;
+
+		for (j = 1; j < 8; j++)
+		{
+			if ((key[i] >> j) & 0x1)
+			{
+				parity = ~parity & 0x01;
+			}
+		}
+
+		key[i] |= parity;
+	}
+}
+
+static uint8_t get_conv_cw_index(uint8_t ecmTag)
+{
+	switch (ecmTag)
+	{
+		case PVU_CONVCW_VID_ECM:
+			return PVU_CW_VID;
+
+		case PVU_CONVCW_HSD_ECM:
+			return PVU_CW_HSD;
+
+		case PVU_CONVCW_A1_ECM:
+			return PVU_CW_A1;
+
+		case PVU_CONVCW_A2_ECM:
+			return PVU_CW_A2;
+
+		case PVU_CONVCW_A3_ECM:
+			return PVU_CW_A3;
+
+		case PVU_CONVCW_A4_ECM:
+			return PVU_CW_A4;
+
+		case PVU_CONVCW_UTL_ECM:
+			return PVU_CW_UTL;
+
+		case PVU_CONVCW_VBI_ECM:
+			return PVU_CW_VBI;
+
+		default:
+			return PVU_CW_VBI;
+	}
+}
+
+static uint16_t get_seed_iv(uint8_t seedType, uint8_t *ecm)
+{
+	switch (seedType)
+	{
+		case PVU_CW_VID:
+			return ((ecm[0x10] & 0x1F) << 3) | 0;
+
+		case PVU_CW_HSD:
+			return ((ecm[0x12] & 0x1F) << 3) | 2;
+
+		case PVU_CW_A1:
+			return ((ecm[0x11] & 0x3F) << 3) | 1;
+
+		case PVU_CW_A2:
+			return ((ecm[0x13] & 0x3F) << 3) | 1;
+
+		case PVU_CW_A3:
+			return ((ecm[0x19] & 0x3F) << 3) | 1;
+
+		case PVU_CW_A4:
+			return ((ecm[0x1A] & 0x3F) << 3) | 1;
+
+		case PVU_CW_UTL:
+			return ((ecm[0x14] & 0x0F) << 3) | 4;
+
+		case PVU_CW_VBI:
+			return (((ecm[0x15] & 0xF8) >> 3) << 3) | 5;
+
+		default:
+			return 0;
+	}
+}
+
+static uint8_t expand_seed(uint8_t seedType, uint8_t *seed)
+{
+	uint8_t seedLength = 0, i;
+
+	switch (seedType)
+	{
+		case PVU_CW_VID:
+		case PVU_CW_HSD:
+			seedLength = 4;
+			break;
+
+		case PVU_CW_A1:
+		case PVU_CW_A2:
+		case PVU_CW_A3:
+		case PVU_CW_A4:
+			seedLength = 3;
+			break;
+
+		case PVU_CW_UTL:
+		case PVU_CW_VBI:
+			seedLength = 2;
+			break;
+
+		default:
+			return seedLength;
+	}
+
+	for (i = seedLength; i < 7; i++)
+	{
+		seed[i] = seed[i % seedLength];
+	}
+
+	return seedLength;
+}
+
+static void calculate_seed(uint8_t seedType, uint8_t *ecm, uint8_t *seedBase,
+							uint8_t *key, uint8_t *seed, uint8_t sbox)
+{
+	uint16_t tmpSeed;
+
+	tmpSeed = get_seed_iv(seedType, ecm + 23);
+
+	seed[0] = (tmpSeed >> 2) & 0xFF;
+	seed[1] = ((tmpSeed & 0x3) << 6) | (seedBase[0] >> 2);
+	seed[2] = (    seedBase[0] << 6) | (seedBase[1] >> 2);
+	seed[3] = (    seedBase[1] << 6) | (seedBase[2] >> 2);
+	seed[4] = (    seedBase[2] << 6) | (seedBase[3] >> 2);
+	seed[5] = (    seedBase[3] << 6);
+
+	powervu_decrypt(seed, 6, key, sbox);
+
+	seed[0] = (seed[1] << 2) | (seed[2] >> 6);
+	seed[1] = (seed[2] << 2) | (seed[3] >> 6);
+	seed[2] = (seed[3] << 2) | (seed[4] >> 6);
+	seed[3] = (seed[4] << 2) | (seed[5] >> 6);
+}
+
+static void calculate_cw(uint8_t seedType, uint8_t *seed, uint8_t csaUsed, uint8_t *convolvedCw,
+							uint8_t *cw, uint8_t *baseCw, uint8_t *seedEcmCw, uint8_t hashModeCw,
+							uint8_t needsUnmasking, uint8_t xorMode, int modeCW, uint8_t* ecmBody)
+{
+	int32_t k;
+	uint8_t seedLength, val = 0;
+
+	seedLength = expand_seed(seedType, seed);
+
+	if (needsUnmasking && (((modeCW >= 0x00) && (hashModeCw > 0) && (hashModeCw <= 0x27) &&
+		(hashModeCw != 0x0B) && (hashModeCw != 0x0C) && (hashModeCw != 0x0D) && (hashModeCw != 0x0E)) ||
+		(modeCW == 0x03) || (modeCW == 0x04)))
+	{
+		switch (seedType)
+		{
+			case PVU_CW_VID:
+				val = 0;
+				break;
+
+			case PVU_CW_A1:
+			case PVU_CW_A2:
+			case PVU_CW_A3:
+			case PVU_CW_A4:
+				val = 1;
+				break;
+
+			case PVU_CW_HSD:
+				val = 2;
+				break;
+
+			case PVU_CW_UTL:
+				val = 4;
+				break;
+
+			case PVU_CW_VBI:
+				val = 5;
+				break;
+		}
+
+		create_cw(seed, seedLength, baseCw, val, seedEcmCw, cw, csaUsed, hashModeCw, modeCW, ecmBody);
+
+		if (csaUsed)
+		{
+			cw[0] = cw[0] ^ convolvedCw[0];
+			cw[1] = cw[1] ^ convolvedCw[1];
+			cw[2] = cw[2] ^ convolvedCw[2];
+			cw[3] = cw[3] ^ convolvedCw[3];
+			cw[4] = cw[4] ^ convolvedCw[4];
+			cw[5] = cw[5] ^ convolvedCw[5];
+			cw[6] = cw[6] ^ convolvedCw[6];
+			cw[7] = cw[7] ^ convolvedCw[7];
+
+			cw[3] = cw[0] + cw[1] + cw[2];
+			cw[7] = cw[4] + cw[5] + cw[6];
+		}
+	}
+	else
+	{
+		if (csaUsed)
+		{
+			for (k = 0; k < 7; k++)
+			{
+				seed[k] ^= baseCw[k];
+			}
+
+			cw[0] = seed[0] ^ convolvedCw[0];
+			cw[1] = seed[1] ^ convolvedCw[1];
+			cw[2] = seed[2] ^ convolvedCw[2];
+			cw[3] = seed[3] ^ convolvedCw[3];
+			cw[4] = seed[3] ^ convolvedCw[4];
+			cw[5] = seed[4] ^ convolvedCw[5];
+			cw[6] = seed[5] ^ convolvedCw[6];
+			cw[7] = seed[6] ^ convolvedCw[7];
+		}
+		else
+		{
+			if (xorMode == 0)
+			{
+				for (k = 0; k < 7; k++)
+				{
+					cw[k] = seed[k] ^ baseCw[k];
+				}
+			}
+
+			if (xorMode == 1)
+			{
+				for (k = 0; k < 3; k++)
+				{
+					cw[k] = seed[k] ^ baseCw[k];
+				}
+
+				for (k = 3; k < 7; k++)
+				{
+					cw[k] = baseCw[k];
+				}
+			}
+
+			expand_des_key(cw);
+		}
+	}
+}
+
+#ifdef MODULE_STREAMRELAY
+int8_t powervu_ecm(uint8_t *ecm, uint8_t *dw, EXTENDED_CW *cw_ex, uint16_t srvid, uint16_t caid, uint16_t tsid, uint16_t onid, uint32_t ens, emu_stream_client_key_data *cdata)
+#else
+int8_t powervu_ecm(uint8_t *ecm, uint8_t *dw, EXTENDED_CW *cw_ex, uint16_t srvid, uint16_t caid, uint16_t tsid, uint16_t onid, uint32_t ens)
+#endif
+{
+	uint32_t i, j, k;
+	uint32_t ecmCrc32, keyRef0, keyRef1, keyRef2, channel_hash, group_id = 0;
+
+	uint16_t ecmLen = SCT_LEN(ecm);
+	uint16_t nanoLen, channelId, ecmSrvid;
+
+	uint8_t keyIndex, sbox, decrypt_ok, calculateAll, hashModeCw = 0, needsUnmasking, xorMode;
+	uint8_t nanoCmd, nanoChecksum, keyType, fixedKey, oddKey, bid, csaUsed, modeCW = 0, offsetBody;
+
+	uint8_t ecmKey[7], tmpEcmKey[7], seedBase[4], baseCw[7], seed[8][8], cw[8][8], convolvedCw[8][8];
+	uint8_t ecmPart1[14], ecmPart2[27], unmaskedEcm[ecmLen], seedEcmCw[16];
+
+	//char tmpBuffer1[512];
+	char tmpBuffer2[17];
+
+#ifdef MODULE_STREAMRELAY
+	emu_stream_cw_item *cw_item;
+	int8_t update_global_key = 0;
+	int8_t update_global_keys[EMU_STREAM_SERVER_MAX_CONNECTIONS];
+
+	memset(update_global_keys, 0, sizeof(update_global_keys));
+#else
+#define EMU_STREAM_MAX_AUDIO_SUB_TRACKS 4
+#endif
+
+	if (ecmLen < 7)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	needsUnmasking = (ecm[3] & 0xF0) == 0x50;
+
+	//cs_log_dbg(D_ATR, "ecm1: %s", cs_hexdump(0, ecm, ecmLen, tmpBuffer1, sizeof(tmpBuffer1)));
+
+	if (needsUnmasking)
+	{
+		hashModeCw = unmask_ecm(ecm, seedEcmCw, &modeCW);
+	}
+
+	//cs_log_dbg(D_ATR, "needsUnmasking=%d", needsUnmasking);
+	//cs_log_dbg(D_ATR, "ecm2: %s", cs_hexdump(0, ecm, ecmLen, tmpBuffer1, sizeof(tmpBuffer1)));
+
+	memcpy(unmaskedEcm, ecm, ecmLen);
+
+	ecmCrc32 = b2i(4, ecm + ecmLen - 4);
+
+	if (ccitt32_crc(ecm, ecmLen - 4) != ecmCrc32)
+	{
+		return EMU_CHECKSUM_ERROR;
+	}
+	ecmLen -= 4;
+
+	for (i = 0; i < 8; i++)
+	{
+		memset(convolvedCw[i], 0, 8);
+	}
+
+	for (i = 3; i + 3 < ecmLen; )
+	{
+		nanoLen = (((ecm[i] & 0x0F) << 8) | ecm[i + 1]);
+		i += 2;
+
+		if (nanoLen > 0)
+		{
+			nanoLen--;
+		}
+		nanoCmd = ecm[i++];
+
+		if (i + nanoLen > ecmLen)
+		{
+			return EMU_NOT_SUPPORTED;
+		}
+
+		switch (nanoCmd)
+		{
+			case 0x27:
+				if (nanoLen < 15)
+				{
+					break;
+				}
+
+				nanoChecksum = 0;
+				for (j = 4; j < 15; j++)
+				{
+					nanoChecksum += ecm[i + j];
+				}
+
+				if (nanoChecksum != 0)
+				{
+					break;
+				}
+
+				keyType = get_conv_cw_index(ecm[i + 4]);
+				memcpy(convolvedCw[keyType], &ecm[i + 6], 8);
+				break;
+
+			default:
+				break;
+		}
+
+		i += nanoLen;
+	}
+
+	for (i = 3; i + 3 < ecmLen; )
+	{
+		nanoLen = (((ecm[i] & 0x0F) << 8) | ecm[i + 1]);
+		i += 2;
+
+		if (nanoLen > 0)
+		{
+			nanoLen--;
+		}
+		nanoCmd = ecm[i++];
+
+		if (i + nanoLen > ecmLen)
+		{
+			return EMU_NOT_SUPPORTED;
+		}
+
+		switch (nanoCmd)
+		{
+			case 0x20:
+			{
+				if (nanoLen < 54)
+				{
+					break;
+				}
+
+				offsetBody = i + 4 + ecm[i + 3];
+				i += ecm[i + 3]; // Extra Data Length
+
+				csaUsed = get_bit(ecm[i + 7], 7);
+				fixedKey = !get_bit(ecm[i + 6], 5);
+				oddKey = get_bit(ecm[i + 6], 4);
+				xorMode = get_bit(ecm[i + 6], 0);
+				bid = (get_bit(ecm[i + 7], 1) << 1) | get_bit(ecm[i + 7], 0);
+				sbox = get_bit(ecm[i + 6], 3);
+
+				keyIndex = (fixedKey << 3) | (bid << 2) | oddKey;
+				channelId = b2i(2, ecm + i + 23);
+				ecmSrvid = (channelId >> 4) | ((channelId & 0xF) << 12);
+
+				cs_log_dbg(D_ATR, "csaUsed: %d, xorMode: %d, ecmSrvid: %04X (%d), hashModeCw: %d, modeCW: %d",
+							csaUsed, xorMode, ecmSrvid, srvid, hashModeCw, modeCW);
+
+				channel_hash = create_channel_hash(caid, tsid, onid, ens);
+				group_id = get_channel_group(channel_hash);
+
+				cs_log_dbg(D_ATR, "channel hash: %08X, group id: %04X", channel_hash, group_id);
+
+				decrypt_ok = 0;
+
+				memcpy(ecmPart1, ecm + i + 8, 14);
+				memcpy(ecmPart2, ecm + i + 27, 27);
+
+				keyRef0 = 0;
+				keyRef1 = 0;
+				keyRef2 = 0;
+
+				do
+				{
+					if (!group_id || !get_ecm_key(ecmKey, group_id << 16, 0x0000FFFF, keyIndex, keyRef0++))
+					{
+						if (!get_ecm_key(ecmKey, ecmSrvid, 0xFFFF0000, keyIndex, keyRef1++))
+						{
+							if (!get_ecm_key(ecmKey, channelId, 0xFFFF0000, keyIndex, keyRef2++))
+							{
+								cs_log("Key not found or invalid: P ****%04X %02X", ecmSrvid, keyIndex);
+
+								if (group_id) // Print only if there is a matching "GROUP" entry
+								{
+									cs_log("Key not found or invalid: P %04XFFFF %02X", group_id, keyIndex);
+								}
+
+								return EMU_KEY_NOT_FOUND;
+							}
+						}
+					}
+
+					powervu_decrypt(ecm + i + 8, 14, ecmKey, sbox);
+
+					if ((ecm[i + 6] != ecm[i + 6 + 7]) || (ecm[i + 6 + 8] != ecm[i + 6 + 15]))
+					{
+						memcpy(ecm + i + 8, ecmPart1, 14);
+						continue;
+					}
+
+					memcpy(tmpEcmKey, ecmKey, 7);
+
+					powervu_decrypt(ecm + i + 27, 27, ecmKey, sbox);
+
+					if ((ecm[i + 23] != ecm[i + 23 + 29]) || (ecm[i + 23 + 1] != ecm[i + 23 + 30]))
+					{
+						memcpy(ecm + i + 8, ecmPart1, 14);
+						memcpy(ecm + i + 27, ecmPart2, 27);
+						continue;
+					}
+
+					decrypt_ok = 1;
+				}
+				while (!decrypt_ok);
+
+				memcpy(seedBase, ecm + i + 6 + 2, 4);
+
+#ifdef MODULE_STREAMRELAY
+				if (cdata == NULL)
+				{
+					SAFE_MUTEX_LOCK(&emu_fixed_key_srvid_mutex);
+					for (j = 0; j < EMU_STREAM_SERVER_MAX_CONNECTIONS; j++)
+					{
+						if (!stream_server_has_ecm[j] && emu_stream_cur_srvid[j] == srvid)
+						{
+							update_global_key = 1;
+							update_global_keys[j] = 1;
+						}
+					}
+					SAFE_MUTEX_UNLOCK(&emu_fixed_key_srvid_mutex);
+				}
+
+				calculateAll = cdata != NULL || update_global_key || cw_ex != NULL;
+#else
+				calculateAll = cw_ex != NULL;
+#endif
+
+				if (calculateAll) // Calculate all seeds
+				{
+					for (j = 0; j < 8; j++)
+					{
+						memcpy(ecmKey, tmpEcmKey, 7);
+						calculate_seed(j, ecm + i, seedBase, ecmKey, seed[j], sbox);
+					}
+				}
+				else // Calculate only video seed
+				{
+					memcpy(ecmKey, tmpEcmKey, 7);
+					calculate_seed(PVU_CW_VID, ecm + i, seedBase, ecmKey, seed[PVU_CW_VID], sbox);
+				}
+
+				memcpy(baseCw, ecm + i + 6 + 8, 7);
+
+				if (calculateAll) // Calculate all CWs
+				{
+					for (j = 0; j < 8; j++)
+					{
+						calculate_cw(j, seed[j], csaUsed, convolvedCw[j], cw[j], baseCw, seedEcmCw,
+									hashModeCw, needsUnmasking, xorMode, modeCW, unmaskedEcm + offsetBody);
+
+						if (csaUsed)
+						{
+							for (k = 0; k < 8; k += 4)
+							{
+								cw[j][k + 3] = ((cw[j][k] + cw[j][k + 1] + cw[j][k + 2]) & 0xFF);
+							}
+						}
+
+						cs_log_dbg(D_ATR, "calculated cw %d: %s", j,
+										cs_hexdump(0, cw[j], 8, tmpBuffer2, sizeof(tmpBuffer2)));
+					}
+
+					//cs_log_dbg(D_ATR, "csaUsed=%d, cw: %s cdata=%x, cw_ex=%x",
+					//			csaUsed, cs_hexdump(3, cw[0], 8, tmpBuffer1, sizeof(tmpBuffer1)),
+					//			(unsigned int)cdata, (unsigned int)cw_ex);
+
+#ifdef MODULE_STREAMRELAY
+					if (update_global_key)
+					{
+						for (j = 0; j < EMU_STREAM_SERVER_MAX_CONNECTIONS; j++)
+						{
+							if (update_global_keys[j])
+							{
+								cw_item = (emu_stream_cw_item *)malloc(sizeof(emu_stream_cw_item));
+								if (cw_item != NULL)
+								{
+									cw_item->csa_used = csaUsed;
+									cw_item->is_even = ecm[0] == 0x80 ? 1 : 0;
+									cs_ftime(&cw_item->write_time);
+									add_ms_to_timeb(&cw_item->write_time, cfg.emu_stream_ecm_delay);
+									memcpy(cw_item->cw, cw, sizeof(cw));
+									ll_append(ll_emu_stream_delayed_keys[j], cw_item);
+								}
+							}
+						}
+					}
+
+					if (cdata != NULL)
+					{
+						for (j = 0; j < EMU_STREAM_MAX_AUDIO_SUB_TRACKS + 2; j++)
+						{
+							if (csaUsed)
+							{
+								if (ecm[0] == 0x80)
+								{
+									if (has_dvbcsa_ecm)
+									{
+										dvbcsa_bs_key_set(cw[j], key_data[cdata->connid].key[j][EVEN]);
+									}
+								}
+								else
+								{
+									if (has_dvbcsa_ecm)
+									{
+										dvbcsa_bs_key_set(cw[j], key_data[cdata->connid].key[j][ODD]);
+									}
+								}
+
+								cdata->csa_used = 1;
+							}
+							else
+							{
+								if (ecm[0] == 0x80)
+								{
+									des_set_key(cw[j], cdata->pvu_des_ks[j][0]);
+								}
+								else
+								{
+									des_set_key(cw[j], cdata->pvu_des_ks[j][1]);
+								}
+
+								cdata->csa_used = 0;
+							}
+						}
+					}
+#endif
+
+					if (cw_ex != NULL)
+					{
+						cw_ex->mode = CW_MODE_MULTIPLE_CW;
+
+						if (csaUsed)
+						{
+							cw_ex->algo = CW_ALGO_CSA;
+							cw_ex->algo_mode = CW_ALGO_MODE_CBC;
+						}
+						else
+						{
+							cw_ex->algo = CW_ALGO_DES;
+							cw_ex->algo_mode = CW_ALGO_MODE_ECB;
+						}
+
+						for (j = 0; j < EMU_STREAM_MAX_AUDIO_SUB_TRACKS; j++)
+						{
+							memset(cw_ex->audio[j], 0, 16);
+
+							if (ecm[0] == 0x80)
+							{
+								memcpy(cw_ex->audio[j], cw[PVU_CW_A1 + j], 8);
+							}
+							else
+							{
+								memcpy(&cw_ex->audio[j][8], cw[PVU_CW_A1 + j], 8);
+							}
+						}
+
+						memset(cw_ex->data, 0, 16);
+
+						if (ecm[0] == 0x80)
+						{
+							memcpy(cw_ex->data, cw[PVU_CW_HSD], 8);
+						}
+						else
+						{
+							memcpy(&cw_ex->data[8], cw[PVU_CW_HSD], 8);
+						}
+					}
+				}
+				else // Calculate only video CW
+				{
+					calculate_cw(PVU_CW_VID, seed[PVU_CW_VID], csaUsed, convolvedCw[PVU_CW_VID],
+								cw[PVU_CW_VID], baseCw, seedEcmCw, hashModeCw, needsUnmasking,
+								xorMode, modeCW, unmaskedEcm + offsetBody);
+
+					if (csaUsed)
+					{
+						for (k = 0; k < 8; k += 4)
+						{
+							cw[PVU_CW_VID][k + 3] = ((cw[PVU_CW_VID][k] + cw[PVU_CW_VID][k + 1] + cw[PVU_CW_VID][k + 2]) & 0xFF);
+						}
+					}
+
+					cs_log_dbg(D_ATR, "calculated video only cw: %s",
+									cs_hexdump(0, cw[PVU_CW_VID], 8, tmpBuffer2, sizeof(tmpBuffer2)));
+				}
+
+				memset(dw, 0, 16);
+
+				if (ecm[0] == 0x80)
+				{
+					memcpy(dw, cw[PVU_CW_VID], 8);
+				}
+				else
+				{
+					memcpy(&dw[8], cw[PVU_CW_VID], 8);
+				}
+
+				return EMU_OK;
+			}
+
+			default:
+				break;
+		}
+
+		i += nanoLen;
+	}
+
+	return EMU_NOT_SUPPORTED;
+}
+
+// PowerVu EMM EMU
+static void create_data_unmask_emm_mode_03(uint8_t *emmBody, uint8_t *data)
+{
+	int i;
+	uint8_t padding[] =
+	{
+		0xB3, 0x60, 0x35, 0xC8, 0x5C, 0x26, 0xC1, 0xD0,
+		0x88, 0x86, 0x57, 0xB6, 0x45, 0xA7, 0xDF, 0x7E,
+		0xF0, 0xA8, 0x49, 0xFB, 0x79, 0x6C, 0xAF, 0xB0
+	};
+
+	memcpy(data + 40, padding, 24);
+
+	for (i = 0; i < 5; i++)
+	{
+		data[0 + i * 8] = emmBody[0x18 + i * 0x1B];
+		data[1 + i * 8] = emmBody[0x16 + i * 0x1B];
+		data[2 + i * 8] = emmBody[0x07 + i * 0x1B];
+		data[3 + i * 8] = emmBody[0x0B + i * 0x1B];
+		data[4 + i * 8] = emmBody[0x06 + i * 0x1B];
+		data[5 + i * 8] = emmBody[0x19 + i * 0x1B];
+		data[6 + i * 8] = emmBody[0x15 + i * 0x1B];
+		data[7 + i * 8] = emmBody[0x03 + i * 0x1B];
+	}
+}
+
+static void create_data_unmask_emm_mode_04(uint8_t *emmBody, uint8_t *data)
+{
+	int i;
+	uint8_t padding[] =
+	{
+		0x56, 0xC7, 0x05, 0x66, 0xC7, 0x4E, 0xC1, 0xA0,
+		0x9E, 0xD1, 0xFE, 0x92, 0xE8, 0xCD, 0x5F, 0xAF,
+		0xCF, 0xE5, 0xE9, 0x9E, 0x7A, 0x38, 0xAC, 0x68
+	};
+
+	memcpy(data + 0x28, padding, 0x18);
+
+	for (i = 0; i < 5; i++)
+	{
+		data[0 + i * 8] = emmBody[0x06 + i * 0x1B];
+		data[1 + i * 8] = emmBody[0x19 + i * 0x1B];
+		data[2 + i * 8] = emmBody[0x16 + i * 0x1B];
+		data[3 + i * 8] = emmBody[0x0A + i * 0x1B];
+		data[4 + i * 8] = emmBody[0x13 + i * 0x1B];
+		data[5 + i * 8] = emmBody[0x05 + i * 0x1B];
+		data[6 + i * 8] = emmBody[0x14 + i * 0x1B];
+		data[7 + i * 8] = emmBody[0x18 + i * 0x1B];
+	}
+}
+
+static uint8_t get_mode_unmask_emm(uint8_t *extraData)
+{
+	uint16_t data = ((uint16_t)extraData[0] << 8) + extraData[1];
+
+	if (data == 0)
+	{
+		return 0x00;
+	}
+
+	switch (data & 0x0881)
+	{
+		case 0x0080:
+		case 0x0881:
+			return 0x01;
+
+		case 0x0001:
+		case 0x0880:
+			return 0x02;
+
+		case 0x0800:
+		case 0x0081:
+			return 0x03;
+
+		case 0x0000:
+		case 0x0801:
+			switch (data & 0x9020)
+			{
+				case 0x8000:
+				case 0x9000:
+					return 0x04;
+
+				case 0x0020:
+				case 0x9020:
+					return 0x05;
+
+				case 0x0000:
+				case 0x1000:
+					return 0x06;
+
+				case 0x1020:
+				case 0x8020:
+					switch (data & 0x2014)
+					{
+						case 0x2004:
+						case 0x2010:
+							return 0x07;
+
+						case 0x0000:
+						case 0x0004:
+							return 0x08;
+
+						case 0x0014:
+						case 0x2014:
+							return 0x09;
+
+						case 0x0010:
+						case 0x2000:
+							return 0x00;
+					}
+					break;
+			}
+			break;
+	}
+	return 0x00;
+}
+
+static void unmask_emm(uint8_t *emm)
+{
+	uint32_t crc, i, l;
+	uint8_t hashModeEmm, modeUnmask, data[64], mask[16];
+
+	uint8_t sourcePos[] =
+	{
+		0x03, 0x0C, 0x0D, 0x11, 0x15, 0x18, 0x1D, 0x1F, 0x25, 0x2A,
+		0x32, 0x35, 0x3A, 0x3B, 0x3E, 0x42, 0x47, 0x48, 0x53, 0x58,
+		0x5C, 0x61, 0x66, 0x69, 0x71, 0x72, 0x78, 0x7B, 0x81, 0x84
+	};
+
+	uint8_t destPos[] =
+	{
+		0x02, 0x08, 0x0B, 0x0E, 0x13, 0x16, 0x1E, 0x23, 0x28, 0x2B,
+		0x2F, 0x33, 0x38, 0x3C, 0x40, 0x44, 0x4A, 0x4D, 0x54, 0x57,
+		0x5A, 0x63, 0x68, 0x6A, 0x70, 0x75, 0x76, 0x7D, 0x82, 0x85
+	};
+
+	// Create Mask for ECM decryption
+	create_data_ecm_emm(emm, sourcePos, 19, 30, data);
+
+	hashModeEmm = emm[8] ^ crc8_calc(data, 30);
+	modeUnmask = get_mode_unmask_emm(emm + 16);
+
+	if ((modeUnmask == 0x00) || (modeUnmask > 4))
+	{
+		create_hash(data, 30, mask, hashModeEmm);
+
+		// Unmask Body
+		for (i = 0; i < 30; i++)
+		{
+			emm[19 + destPos[i]] ^= mask[i & 0x0F];
+		}
+	}
+	else if (modeUnmask == 0x03)
+	{
+		for (i = 0; i < 5; i++)
+		{
+			emm[0x13 + 0x03 + i * 0x1B] -= emm[0x13 + 0x0D + i * 0x1B];
+			emm[0x13 + 0x06 + i * 0x1B] -= emm[0x13 + 0x1A + i * 0x1B];
+			emm[0x13 + 0x07 + i * 0x1B] -= emm[0x13 + 0x10 + i * 0x1B];
+			emm[0x13 + 0x0B + i * 0x1B] -= emm[0x13 + 0x17 + i * 0x1B];
+			emm[0x13 + 0x15 + i * 0x1B] -= emm[0x13 + 0x05 + i * 0x1B];
+			emm[0x13 + 0x16 + i * 0x1B] -= emm[0x13 + 0x0F + i * 0x1B];
+			emm[0x13 + 0x18 + i * 0x1B] -= emm[0x13 + 0x14 + i * 0x1B];
+			emm[0x13 + 0x19 + i * 0x1B] -= emm[0x13 + 0x04 + i * 0x1B];
+		}
+
+		create_data_unmask_emm_mode_03(emm + 0x13, data);
+		create_hash_mode_03(data, mask);
+
+		for (i = 0; i < 5; i++)
+		{
+			emm[0x13 + 0x14 + i * 0x1B] ^= mask[0x00];
+			emm[0x13 + 0x0F + i * 0x1B] ^= mask[0x01];
+			emm[0x13 + 0x10 + i * 0x1B] ^= mask[0x02];
+			emm[0x13 + 0x17 + i * 0x1B] ^= mask[0x03];
+			emm[0x13 + 0x1A + i * 0x1B] ^= mask[0x04];
+			emm[0x13 + 0x04 + i * 0x1B] ^= mask[0x05];
+			emm[0x13 + 0x05 + i * 0x1B] ^= mask[0x06];
+			emm[0x13 + 0x0D + i * 0x1B] ^= mask[0x07];
+			emm[0x13 + 0x09 + i * 0x1B] ^= mask[0x08];
+			emm[0x13 + 0x0A + i * 0x1B] ^= mask[0x09];
+			emm[0x13 + 0x0E + i * 0x1B] ^= mask[0x0A];
+			emm[0x13 + 0x11 + i * 0x1B] ^= mask[0x0B];
+			emm[0x13 + 0x12 + i * 0x1B] ^= mask[0x0C];
+			emm[0x13 + 0x13 + i * 0x1B] ^= mask[0x0D];
+			emm[0x13 + 0x08 + i * 0x1B] ^= mask[0x0E];
+			emm[0x13 + 0x0C + i * 0x1B] ^= mask[0x0F];
+		}
+	}
+	else if (modeUnmask == 0x04)
+	{
+		for (i = 0; i < 5; i++)
+		{
+			emm[0x13 + 0x05 + i * 0x1B] -= emm[0x13 + 0x04 + i * 0x1B];
+			emm[0x13 + 0x06 + i * 0x1B] -= emm[0x13 + 0x0B + i * 0x1B];
+			emm[0x13 + 0x0A + i * 0x1B] -= emm[0x13 + 0x17 + i * 0x1B];
+			emm[0x13 + 0x13 + i * 0x1B] -= emm[0x13 + 0x1A + i * 0x1B];
+			emm[0x13 + 0x14 + i * 0x1B] -= emm[0x13 + 0x0E + i * 0x1B];
+			emm[0x13 + 0x16 + i * 0x1B] -= emm[0x13 + 0x15 + i * 0x1B];
+			emm[0x13 + 0x18 + i * 0x1B] -= emm[0x13 + 0x08 + i * 0x1B];
+			emm[0x13 + 0x19 + i * 0x1B] -= emm[0x13 + 0x12 + i * 0x1B];
+		}
+
+		create_data_unmask_emm_mode_04(emm + 0x13, data);
+		create_hash_mode_04(data, mask);
+
+		for (i = 0; i < 5; i++)
+		{
+			emm[0x13 + 0x0B + i * 0x1B] ^= mask[0x00];
+			emm[0x13 + 0x12 + i * 0x1B] ^= mask[0x01];
+			emm[0x13 + 0x15 + i * 0x1B] ^= mask[0x02];
+			emm[0x13 + 0x17 + i * 0x1B] ^= mask[0x03];
+			emm[0x13 + 0x1A + i * 0x1B] ^= mask[0x04];
+			emm[0x13 + 0x04 + i * 0x1B] ^= mask[0x05];
+			emm[0x13 + 0x0E + i * 0x1B] ^= mask[0x06];
+			emm[0x13 + 0x08 + i * 0x1B] ^= mask[0x07];
+			emm[0x13 + 0x09 + i * 0x1B] ^= mask[0x08];
+			emm[0x13 + 0x0C + i * 0x1B] ^= mask[0x09];
+			emm[0x13 + 0x03 + i * 0x1B] ^= mask[0x0A];
+			emm[0x13 + 0x0F + i * 0x1B] ^= mask[0x0B];
+			emm[0x13 + 0x10 + i * 0x1B] ^= mask[0x0C];
+			emm[0x13 + 0x07 + i * 0x1B] ^= mask[0x0D];
+			emm[0x13 + 0x0D + i * 0x1B] ^= mask[0x0E];
+			emm[0x13 + 0x11 + i * 0x1B] ^= mask[0x0F];
+		}
+	}
+	else
+	{
+		cs_log("A new unknown emm mode [%d] is in use.", modeUnmask);
+	}
+
+	// Fix Header
+	emm[3] &= 0x0F;
+	emm[3] |= 0x10;
+	emm[8] = 0x00;
+
+	// Fix CRC (optional)
+	l = (((emm[1] << 8) + emm[2]) & 0xFFF) + 3 - 4;
+	crc = ccitt32_crc(emm, l);
+
+	emm[l + 0] = crc >> 24;
+	emm[l + 1] = crc >> 16;
+	emm[l + 2] = crc >> 8;
+	emm[l + 3] = crc >> 0;
+}
+
+static int8_t update_ecm_keys_by_group(uint32_t groupId, uint8_t keyIndex, uint8_t *Key, uint32_t uniqueAddress)
+{
+	int8_t ret = 0;
+	uint8_t oldKey[7];
+	uint32_t foundProvider = 0, keyRef = 0;
+	char indexStr[3], uaInfo[13];
+
+	snprintf(indexStr, 3, "%02X", keyIndex);
+	snprintf(uaInfo, 13, "UA: %08X", uniqueAddress);
+
+	SAFE_MUTEX_LOCK(&emu_key_data_mutex);
+	while (emu_find_key('P', groupId << 16 & 0xFFFF0000, 0x0000FFFF, indexStr, oldKey, 7, 0, keyRef, 0, &foundProvider))
+	{
+		keyRef++;
+
+		if (memcmp(oldKey, Key, 7) == 0) // New ECM key already in the db
+		{
+			continue;
+		}
+
+		if (emu_set_key('P', foundProvider, indexStr, Key, 7, 1, uaInfo, NULL))
+		{
+			ret = 1;
+		}
+	}
+	SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
+
+	return ret;
+}
+
+int8_t powervu_emm(uint8_t *emm, uint32_t *keysAdded)
+{
+	uint8_t emmInfo, emmType, decryptOk = 0;
+	uint8_t emmKey[7], tmpEmmKey[7], tmp[26];
+	uint16_t emmLen = SCT_LEN(emm);
+	uint32_t i, uniqueAddress, groupId, keyRef = 0;
+	//uint32_t emmCrc32;
+	char keyName[EMU_MAX_CHAR_KEYNAME], keyValue[16];
+
+	if (emmLen < 50)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	// Check if unmasking is needed
+	if ((emm[3] & 0xF0) == 0x50)
+	{
+		unmask_emm(emm);
+	}
+
+	// looks like checksum does not work for all EMMs
+	//emmCrc32 = b2i(4, emm+emmLen-4);
+	//
+	//if(ccitt32_crc(emm, emmLen-4) != emmCrc32)
+	//{
+	//	return EMU_CHECKSUM_ERROR;
+	//}
+	emmLen -= 4;
+
+	uniqueAddress = b2i(4, emm + 12);
+	snprintf(keyName, EMU_MAX_CHAR_KEYNAME, "%.8X", uniqueAddress);
+
+	do
+	{
+		if (!get_emm_key(emmKey, keyName, keyRef++, &groupId))
+		{
+			//cs_log_dbg(D_ATR, "EMM key for UA %s is missing", keyName);
+			return EMU_KEY_NOT_FOUND;
+		}
+
+		for (i = 19; i + 27 <= emmLen; i += 27)
+		{
+			emmInfo = emm[i];
+
+			if (!get_bit(emmInfo, 7))
+			{
+				continue;
+			}
+
+			//keyNb = emm[i] & 0x0F;
+
+			memcpy(tmp, emm + i + 1, 26);
+			memcpy(tmpEmmKey, emmKey, 7);
+			powervu_decrypt(emm + i + 1, 26, tmpEmmKey, 0);
+
+			if ((emm[13] != emm[i + 24]) || (emm[14] != emm[i + 25]) || (emm[15] != emm[i + 26]))
+			{
+				memcpy(emm + i + 1, tmp, 26);
+				memcpy(tmpEmmKey, emmKey, 7);
+				powervu_decrypt(emm + i + 1, 26, tmpEmmKey, 1);
+
+				if ((emm[13] != emm[i + 24]) || (emm[14] != emm[i + 25]) || (emm[15] != emm[i + 26]))
+				{
+					memcpy(emm + i + 1, tmp, 26);
+					memcpy(tmpEmmKey, emmKey, 7);
+					continue;
+				}
+			}
+
+			decryptOk = 1;
+
+			emmType = emm[i + 2] & 0x7F;
+
+			if (emmType > 1)
+			{
+				continue;
+			}
+
+			if (emm[i + 3] == 0 && emm[i + 4] == 0)
+			{
+				cs_hexdump(0, &emm[i + 3], 7, keyValue, sizeof(keyValue));
+				cs_log("Key found in EMM: P %04X**** %02X %s -> REJECTED (looks invalid) UA: %08X",
+						groupId, emmType, keyValue, uniqueAddress);
+				continue;
+			}
+
+			update_ecm_keys_by_group(groupId, emmType, &emm[i + 3], uniqueAddress);
+
+			(*keysAdded)++;
+			cs_hexdump(0, &emm[i + 3], 7, keyValue, sizeof(keyValue));
+			cs_log("Key found in EMM: P %04X**** %02X %s ; UA: %08X", groupId, emmType, keyValue, uniqueAddress);
+		}
+
+	} while (!decryptOk);
+
+	return EMU_OK;
+}
+
+int8_t powervu_get_hexserials(uint8_t hexserials[][4], uint32_t maxCount, uint16_t srvid)
+{
+	//srvid == 0xFFFF -> get all
+
+	int8_t alreadyAdded;
+	uint8_t tmp[4];
+	uint32_t i, j, k, groupid, length, count = 0;
+	KeyDataContainer *KeyDB;
+
+	KeyDB = emu_get_key_container('P');
+	if (KeyDB == NULL)
+	{
+		return 0;
+	}
+
+	for (i = 0; i < KeyDB->keyCount && count < maxCount; i++)
+	{
+		if (KeyDB->EmuKeys[i].provider <= 0x0000FFFF) // skip EMM keys
+		{
+			continue;
+		}
+
+		if (srvid != 0xFFFF && (KeyDB->EmuKeys[i].provider & 0x0000FFFF) != srvid)
+		{
+			continue;
+		}
+
+		// This "groupid" has an ECM key with our "srvid"
+		// (in ECM keys "groupid" is top 16 bits)
+		groupid = KeyDB->EmuKeys[i].provider >> 16;
+
+		for (j = 0; j < KeyDB->keyCount && count < maxCount; j++)
+		{
+			// Skip EMM keys belonging to other groups
+			// (in EMM keys "groupid" is bottom 16 bits)
+			if (KeyDB->EmuKeys[j].provider != groupid)
+			{
+				continue;
+			}
+
+			length = cs_strlen(KeyDB->EmuKeys[j].keyName);
+
+			if (length < 3)
+			{
+				continue;
+			}
+
+			if (length > 8)
+			{
+				length = 8;
+			}
+
+			memset(tmp, 0, 4);
+			char_to_bin(tmp + (4 - (length / 2)), KeyDB->EmuKeys[j].keyName, length);
+
+			for (k = 0, alreadyAdded = 0; k < count; k++)
+			{
+				if (!memcmp(hexserials[k], tmp, 4))
+				{
+					alreadyAdded = 1;
+					break;
+				}
+			}
+
+			if (!alreadyAdded)
+			{
+				memcpy(hexserials[count], tmp, 4);
+				count++;
+			}
+		}
+	}
+
+	return count;
+}
+
+int8_t powervu_get_hexserials_new(uint8_t hexserials[][4], uint32_t maxCount, uint16_t caid,
+									uint16_t tsid, uint16_t onid, uint32_t ens)
+{
+	int8_t alreadyAdded;
+	uint8_t tmp[4];
+	uint32_t i, j, channel_hash, group_id, length, count = 0;
+	KeyDataContainer *KeyDB;
+
+	KeyDB = emu_get_key_container('P');
+	if (KeyDB == NULL)
+	{
+		return 0;
+	}
+
+	channel_hash = create_channel_hash(caid, tsid, onid, ens);
+	group_id = get_channel_group(channel_hash);
+
+	if (group_id == 0) // No group found for this hash
+	{
+		return 0;
+	}
+
+	for (i = 0; i < KeyDB->keyCount && count < maxCount; i++)
+	{
+		// Skip EMM keys belonging to other groups
+		// (in EMM keys "groupid" is bottom 16 bits)
+		if (KeyDB->EmuKeys[i].provider != group_id)
+		{
+			continue;
+		}
+
+		length = cs_strlen(KeyDB->EmuKeys[i].keyName);
+
+		if (length < 3)
+		{
+			continue;
+		}
+
+		if (length > 8)
+		{
+			length = 8;
+		}
+
+		memset(tmp, 0, 4);
+		char_to_bin(tmp + (4 - (length / 2)), KeyDB->EmuKeys[i].keyName, length);
+
+		for (j = 0, alreadyAdded = 0; j < count; j++)
+		{
+			if (!memcmp(hexserials[j], tmp, 4))
+			{
+				alreadyAdded = 1;
+				break;
+			}
+		}
+
+		if (!alreadyAdded)
+		{
+			memcpy(hexserials[count], tmp, 4);
+			count++;
+		}
+	}
+
+	return count;
+}
+
+#endif // WITH_EMU
diff --git a/module-emulator-powervu.h b/module-emulator-powervu.h
new file mode 100644
index 00000000..b50d2088
--- /dev/null
+++ b/module-emulator-powervu.h
@@ -0,0 +1,63 @@
+#ifndef MODULE_EMULATOR_POWERVU_H
+#define MODULE_EMULATOR_POWERVU_H
+
+#ifdef WITH_EMU
+
+#define PVU_CW_VID	0	// VIDeo
+#define PVU_CW_HSD	1	// High Speed Data
+#define PVU_CW_A1	2	// Audio 1
+#define PVU_CW_A2	3	// Audio 2
+#define PVU_CW_A3	4	// Audio 3
+#define PVU_CW_A4	5	// Audio 4
+#define PVU_CW_UTL	6	// UTiLity
+#define PVU_CW_VBI	7	// Vertical Blanking Interval
+
+#define PVU_CONVCW_VID_ECM	0x80	// VIDeo
+#define PVU_CONVCW_HSD_ECM	0x40	// High Speed Data
+#define PVU_CONVCW_A1_ECM	0x20	// Audio 1
+#define PVU_CONVCW_A2_ECM	0x10	// Audio 2
+#define PVU_CONVCW_A3_ECM	0x08	// Audio 3
+#define PVU_CONVCW_A4_ECM	0x04	// Audio 4
+#define PVU_CONVCW_UTL_ECM	0x02	// UTiLity
+#define PVU_CONVCW_VBI_ECM	0x01	// Vertical Blanking Interval
+
+#ifdef MODULE_STREAMRELAY
+int8_t powervu_ecm(uint8_t *ecm, uint8_t *dw, EXTENDED_CW *cw_ex, uint16_t srvid, uint16_t caid, uint16_t tsid, uint16_t onid, uint32_t ens, emu_stream_client_key_data *cdata);
+#else
+int8_t powervu_ecm(uint8_t *ecm, uint8_t *dw, EXTENDED_CW *cw_ex, uint16_t srvid, uint16_t caid, uint16_t tsid, uint16_t onid, uint32_t ens);
+#endif
+int8_t powervu_emm(uint8_t *emm, uint32_t *keysAdded);
+
+/*
+ * This function searches for EMM keys and adds their Unique Addresses (UA) as EMM filters.
+ * The EMM keys are picked from all group id's that have ECM keys for the srvid specified
+ * as input. If there is a large ammount of EMM keys matching these criteria, only the first
+ * "maxCount" UA's are added as EMM filters. The rest are not used at all.
+ *
+ * In the rare case where two or more EMM keys with the same UA belong to different groups,
+ * and these groups also have ECM keys for the srvid in request, there is a chance the ECM
+ * keys in the "wrong" group to be updated. This is because the EMM algorithm has no way of
+ * knowing in which group the service id belongs to. A workaround for this designing flaw
+ * is to make sure there are no EMM keys with the same UA between different groups.
+ *
+ * Hexserials must be of type "uint8_t hexserials[maxCount][4]". If srvid is equal to 0xFFFF
+ * all serials are added (no service id filtering is done). Returns the count of hexserials
+ * added as filters.
+*/
+int8_t powervu_get_hexserials(uint8_t hexserials[][4], uint32_t maxCount, uint16_t srvid);
+
+/*
+ * Like the previous function, it adds UAs as EMM filters. It is used in conjunction with the
+ * new method of entering ECM keys, where one key can serve every channel in the group. Since
+ * there is no srvid to search for, we need to know the group id prior to searching for EMM
+ * keys. To do so, this function calulates a hash using the tsid, onid and enigma namespace of
+ * the transponder, which is only available in enigma2.
+ *
+ * Hexserials must be of type "uint8_t hexserials[maxCount][4]" like before. It returns the
+ * count of hexserials added as filters.
+*/
+int8_t powervu_get_hexserials_new(uint8_t hexserials[][4], uint32_t maxCount, uint16_t caid, uint16_t tsid, uint16_t onid, uint32_t ens);
+
+#endif // WITH_EMU
+
+#endif // MODULE_EMULATOR_POWERVU_H
diff --git a/module-emulator-viaccess.c b/module-emulator-viaccess.c
new file mode 100644
index 00000000..b5fea651
--- /dev/null
+++ b/module-emulator-viaccess.c
@@ -0,0 +1,1183 @@
+#define MODULE_LOG_PREFIX "emu"
+
+#include "globals.h"
+
+#ifdef WITH_EMU
+
+#include "cscrypt/des.h"
+#include "module-emulator-osemu.h"
+#include "module-newcamd-des.h"
+#include "oscam-aes.h"
+#include "oscam-string.h"
+
+// from reader-viaccess.c:
+void hdSurEncPhase1_D2_0F_11(uint8_t *CWs);
+void hdSurEncPhase2_D2_0F_11(uint8_t *CWs);
+void hdSurEncPhase1_D2_13_15(uint8_t *cws);
+void hdSurEncPhase2_D2_13_15(uint8_t *cws);
+
+// Viaccess EMU
+
+static int8_t get_key(uint8_t *buf, uint32_t ident, char keyName, uint32_t keyIndex,
+						uint32_t keyLength, uint8_t isCriticalKey)
+{
+	char keyStr[EMU_MAX_CHAR_KEYNAME];
+	snprintf(keyStr, EMU_MAX_CHAR_KEYNAME, "%c%X", keyName, keyIndex);
+
+	if (emu_find_key('V', ident, 0, keyStr, buf, keyLength, isCriticalKey, 0, 0, NULL))
+	{
+		return 1;
+	}
+
+	if (ident == 0xD00040 && emu_find_key('V', 0x030B00, 0, keyStr, buf, keyLength, isCriticalKey, 0, 0, NULL))
+	{
+		return 1;
+	}
+
+	return 0;
+}
+
+static void via1_mod(const uint8_t *key2, uint8_t *data)
+{
+	int32_t kb, db;
+
+	for (db = 7; db >= 0; db--)
+	{
+		for (kb = 7; kb > 3; kb--)
+		{
+			int32_t a0 = kb ^ db;
+			int32_t pos = 7;
+
+			if (a0 & 4)
+			{
+				a0 ^= 7;
+				pos ^= 7;
+			}
+
+			a0 = (a0 ^ (kb & 3)) + (kb & 3);
+
+			if (!(a0 & 4))
+			{
+				data[db] ^= (key2[kb] ^ ((data[kb ^ pos] * key2[kb ^ 4]) & 0xFF));
+			}
+		}
+	}
+
+	for (db = 0; db < 8; db++)
+	{
+		for (kb = 0; kb < 4; kb++)
+		{
+			int32_t a0 = kb ^ db;
+			int32_t pos = 7;
+
+			if (a0 & 4)
+			{
+				a0 ^= 7;
+				pos ^= 7;
+			}
+
+			a0 = (a0 ^ (kb & 3)) + (kb & 3);
+
+			if (!(a0 & 4))
+			{
+				data[db] ^= (key2[kb] ^ ((data[kb ^ pos] * key2[kb ^ 4]) & 0xFF));
+			}
+		}
+	}
+}
+
+static void via1_decode(uint8_t *data, uint8_t *key)
+{
+	via1_mod(key + 8, data);
+	nc_des(key, DES_ECM_CRYPT, data);
+	via1_mod(key + 8, data);
+}
+
+static void via1_hash(uint8_t *data, uint8_t *key)
+{
+	via1_mod(key + 8, data);
+	nc_des(key, DES_ECM_HASH, data);
+	via1_mod(key + 8, data);
+}
+
+static inline void via1_do_hash(uint8_t *hashbuffer, uint8_t *pH, uint8_t data, uint8_t *hashkey)
+{
+	hashbuffer[*pH] ^= data;
+	(*pH)++;
+
+	if (*pH == 8)
+	{
+		via1_hash(hashbuffer, hashkey);
+		*pH = 0;
+	}
+}
+
+static int8_t via1_decrypt(uint8_t *ecm, uint8_t *dw, uint32_t ident, uint8_t desKeyIndex)
+{
+	int32_t msg_pos, encStart = 0, hash_start, i;
+
+	uint8_t tmp, k, pH, foundData = 0;
+	uint8_t work_key[16], signature[8], hashbuffer[8], prepared_key[16], hashkey[16];
+	uint8_t *data, *des_data1, *des_data2;
+
+	uint16_t ecmLen = SCT_LEN(ecm);
+
+	if (ident == 0)
+	{
+		return EMU_CORRUPT_DATA;
+	}
+
+	memset(work_key, 0, 16);
+
+	if (!get_key(work_key, ident, '0', desKeyIndex, 8, 1))
+	{
+		return EMU_KEY_NOT_FOUND;
+	}
+
+	if (ecmLen < 11)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	data = ecm + 9;
+	des_data1 = dw;
+	des_data2 = dw + 8;
+
+	msg_pos = 0;
+	pH = 0;
+	memset(hashbuffer, 0, sizeof(hashbuffer));
+	memcpy(hashkey, work_key, sizeof(hashkey));
+	memset(signature, 0, 8);
+
+	while (9 + msg_pos + 2 < ecmLen)
+	{
+		switch (data[msg_pos])
+		{
+			case 0xEA:
+				if (9 + msg_pos + 2 + 15 < ecmLen)
+				{
+					encStart = msg_pos + 2;
+					memcpy(des_data1, &data[msg_pos + 2], 8);
+					memcpy(des_data2, &data[msg_pos + 2 + 8], 8);
+					foundData |= 1;
+				}
+				break;
+
+			case 0xF0:
+				if (9 + msg_pos + 2 + 7 < ecmLen)
+				{
+					memcpy(signature, &data[msg_pos + 2], 8);
+					foundData |= 2;
+				}
+				break;
+		}
+		msg_pos += data[msg_pos + 1] + 2;
+	}
+
+	if (foundData != 3)
+	{
+		return EMU_NOT_SUPPORTED;
+	}
+
+	pH = i = 0;
+
+	if (data[0] == 0x9F && 10 + data[1] <= ecmLen)
+	{
+		via1_do_hash(hashbuffer, &pH, data[i++], hashkey);
+		via1_do_hash(hashbuffer, &pH, data[i++], hashkey);
+
+		for (hash_start = 0; hash_start < data[1]; hash_start++)
+		{
+			via1_do_hash(hashbuffer, &pH, data[i++], hashkey);
+		}
+
+		while (pH != 0)
+		{
+			via1_do_hash(hashbuffer, &pH, 0, hashkey);
+		}
+	}
+
+	if (work_key[7] == 0)
+	{
+		for (; i < encStart + 16; i++)
+		{
+			via1_do_hash(hashbuffer, &pH, data[i], hashkey);
+		}
+		memcpy(prepared_key, work_key, 8);
+	}
+	else
+	{
+		prepared_key[0] = work_key[2];
+		prepared_key[1] = work_key[3];
+		prepared_key[2] = work_key[4];
+		prepared_key[3] = work_key[5];
+		prepared_key[4] = work_key[6];
+		prepared_key[5] = work_key[0];
+		prepared_key[6] = work_key[1];
+		prepared_key[7] = work_key[7];
+
+		memcpy(prepared_key + 8, work_key + 8, 8);
+
+		if (work_key[7] & 1)
+		{
+			for (; i < encStart; i++)
+			{
+				via1_do_hash(hashbuffer, &pH, data[i], hashkey);
+			}
+
+			k = ((work_key[7] & 0xF0) == 0) ? 0x5A : 0xA5;
+
+			for (i = 0; i < 8; i++)
+			{
+				tmp = des_data1[i];
+				des_data1[i] = (k & hashbuffer[pH] ) ^ tmp;
+				via1_do_hash(hashbuffer, &pH, tmp, hashkey);
+			}
+
+			for (i = 0; i < 8; i++)
+			{
+				tmp = des_data2[i];
+				des_data2[i] = (k & hashbuffer[pH] ) ^ tmp;
+				via1_do_hash(hashbuffer, &pH, tmp, hashkey);
+			}
+		}
+		else
+		{
+			for ( ; i < encStart + 16; i++)
+			{
+				via1_do_hash(hashbuffer, &pH, data[i], hashkey);
+			}
+		}
+	}
+
+	via1_decode(des_data1, prepared_key);
+	via1_decode(des_data2, prepared_key);
+	via1_hash(hashbuffer, hashkey);
+
+	if (memcmp(signature, hashbuffer, 8))
+	{
+		return EMU_CHECKSUM_ERROR;
+	}
+
+	return EMU_OK;
+}
+
+static int8_t via26_process_dw(uint8_t *indata, uint32_t ident, uint8_t desKeyIndex)
+{
+	uint8_t pv1,pv2, i;
+	uint8_t Tmp[8], T1Key[300], P1Key[8], KeyDes1[16], KeyDes2[16], XorKey[8];
+	uint32_t ks1[32], ks2[32];
+
+	if (!get_key(T1Key, ident, 'T', 1, 300, 1))
+	{
+		return 2;
+	}
+
+	if (!get_key(P1Key, ident, 'P', 1, 8, 1))
+	{
+		return 2;
+	}
+
+	if (!get_key(KeyDes1, ident, 'D', 1, 16, 1))
+	{
+		return 2;
+	}
+
+	if (!get_key(KeyDes2, ident, '0', desKeyIndex, 16, 1))
+	{
+		return 2;
+	}
+
+	if (!get_key(XorKey, ident, 'X', 1, 8, 1))
+	{
+		return 2;
+	}
+
+	for (i = 0; i < 8; i++)
+	{
+		pv1 = indata[i];
+		Tmp[i] = T1Key[pv1];
+	}
+
+	for (i = 0; i < 8; i++)
+	{
+		pv1 = P1Key[i];
+		pv2 = Tmp[pv1];
+		indata[i] = pv2;
+	}
+
+	des_set_key(KeyDes1, ks1);
+	des(indata, ks1, 1);
+
+	for (i = 0; i < 8; i++)
+	{
+		indata[i] ^= XorKey[i];
+	}
+
+	des_set_key(KeyDes2, ks1);
+	des_set_key(KeyDes2 + 8, ks2);
+	des(indata, ks1, 0);
+	des(indata, ks2, 1);
+	des(indata, ks1, 0);
+
+	for (i = 0; i < 8; i++)
+	{
+		indata[i] ^= XorKey[i];
+	}
+
+	des_set_key(KeyDes1, ks1);
+	des(indata, ks1, 0);
+
+	for (i = 0; i < 8; i++)
+	{
+		pv1 = indata[i];
+		pv2 = P1Key[i];
+		Tmp[pv2] = pv1;
+	}
+
+	for (i = 0; i < 8; i++)
+	{
+		pv1 = Tmp[i];
+		pv2 = T1Key[pv1];
+		indata[i] = pv2;
+	}
+
+	return 0;
+}
+
+static int8_t via26_decrypt(uint8_t *source, uint8_t *dw, uint32_t ident, uint8_t desKeyIndex)
+{
+	uint8_t tmpData[8], C1[8];
+	uint8_t *pXorVector;
+	int32_t i, j;
+
+	if (ident == 0)
+	{
+		return EMU_CORRUPT_DATA;
+	}
+
+	if (!get_key(C1, ident, 'C', 1, 8, 1))
+	{
+		return EMU_KEY_NOT_FOUND;
+	}
+
+	for (i = 0; i < 2; i++)
+	{
+		memcpy(tmpData, source + i * 8, 8);
+		via26_process_dw(tmpData, ident, desKeyIndex);
+
+		if (i != 0)
+		{
+			pXorVector = source;
+		}
+		else
+		{
+			pXorVector = &C1[0];
+		}
+
+		for (j = 0; j < 8; j++)
+		{
+			dw[i * 8 + j] = tmpData[j] ^ pXorVector[j];
+		}
+
+		// Fix CW checksum bytes
+		for (j = 3; j < 8; j += 4)
+		{
+			dw[i * 8 + j] = (dw[i * 8 + j - 3] + dw[i * 8 + j - 2] + dw[i * 8 + j - 1]) & 0xFF;
+		}
+	}
+
+	return EMU_OK;
+}
+
+static void via3_core(uint8_t *data, uint8_t Off, uint32_t ident, uint8_t *XorKey, uint8_t *T1Key)
+{
+	uint8_t i;
+	uint32_t lR2, lR3, lR4, lR6, lR7;
+
+	switch (ident)
+	{
+		case 0x032820:
+		{
+			for (i = 0; i < 4; i++)
+			{
+				data[i] ^= XorKey[(Off + i) & 0x07];
+			}
+
+			lR2 = (data[0] ^ 0xBD) + data[0];
+			lR3 = (data[3] ^ 0xEB) + data[3];
+			lR2 = (lR2 - lR3) ^ data[2];
+			lR3 = ((0x39 * data[1]) << 2);
+			data[4] = (lR2 | lR3) + data[2];
+
+			lR3 = ((((data[0] + 6) ^ data[0]) | (data[2] << 1)) ^ 0x65) + data[0];
+			lR2 = (data[1] ^ 0xED) + data[1];
+			lR7 = ((data[3] + 0x29) ^ data[3]) * lR2;
+			data[5] = lR7 + lR3;
+
+			lR2 = ((data[2] ^ 0x33) + data[2]) & 0x0A;
+			lR3 = (data[0] + 0xAD) ^ data[0];
+			lR3 = lR3 + lR2;
+			lR2 = data[3] * data[3];
+			lR7 = (lR2 | 1) + data[1];
+			data[6] = (lR3 | lR7) + data[1];
+
+			lR3 = data[1] & 0x07;
+			lR2 = (lR3 - data[2]) & (data[0] | lR2 | 0x01);
+			data[7] = lR2 + data[3];
+
+			for (i = 0; i < 4; i++)
+			{
+				data[i + 4] = T1Key[data[i + 4]];
+			}
+		}
+		break;
+
+		case 0x030B00:
+		{
+			for (i = 0; i < 4; i++)
+			{
+				data[i] ^= XorKey[(Off + i) & 0x07];
+			}
+
+			lR6 = (data[3] + 0x6E) ^ data[3];
+			lR6 = (lR6 * (data[2] << 1)) + 0x17;
+			lR3 = (data[1] + 0x77) ^ data[1];
+			lR4 = (data[0] + 0xD7) ^ data[0];
+			data[4] = ((lR4 & lR3) | lR6) + data[0];
+
+			lR4 = ((data[3] + 0x71) ^ data[3]) ^ 0x90;
+			lR6 = (data[1] + 0x1B) ^ data[1];
+			lR4 = (lR4 * lR6) ^ data[0];
+			data[5] = (lR4 ^ (data[2] << 1)) + data[1];
+
+			lR3 = (data[3] * data[3]) | 0x01;
+			lR4 = (((data[2] ^ 0x35) + data[2]) | lR3) + data[2];
+			lR6 = data[1] ^ (data[0] + 0x4A);
+			data[6] = lR6 + lR4;
+
+			lR3 = (data[0] * (data[2] << 1)) | data[1];
+			lR4 = 0xFE - data[3];
+			lR3 = lR4 ^ lR3;
+			data[7] = lR3 + data[3];
+
+			for (i = 0; i < 4; i++)
+			{
+				data[4 + i] = T1Key[data[4 + i]];
+			}
+		}
+		break;
+
+		default:
+			break;
+	}
+}
+
+static void via3_fct1(uint8_t *data, uint32_t ident, uint8_t *XorKey, uint8_t *T1Key)
+{
+	uint8_t t;
+
+	via3_core(data, 0, ident, XorKey, T1Key);
+
+	switch (ident)
+	{
+		case 0x032820:
+		{
+			t = data[4];
+			data[4] = data[7];
+			data[7] = t;
+		}
+		break;
+
+		case 0x030B00:
+		{
+			t = data[5];
+			data[5] = data[7];
+			data[7] = t;
+		}
+		break;
+
+		default:
+			break;
+	}
+}
+
+static void via3_fct2(uint8_t *data, uint32_t ident, uint8_t *XorKey, uint8_t *T1Key)
+{
+	uint8_t t;
+
+	via3_core(data, 4, ident, XorKey, T1Key);
+
+	switch (ident)
+	{
+		case 0x032820:
+		{
+			t = data[4];
+			data[4] = data[7];
+			data[7] = data[5];
+			data[5] = data[6];
+			data[6] = t;
+		}
+		break;
+
+		case 0x030B00:
+		{
+			t = data[6];
+			data[6] = data[7];
+			data[7] = t;
+		}
+		break;
+
+		default:
+			break;
+	}
+}
+
+static int8_t via3_process_dw(uint8_t *data, uint32_t ident, uint8_t desKeyIndex)
+{
+	uint8_t i;
+	uint8_t tmp[8], T1Key[300], P1Key[8], KeyDes[16], XorKey[8];
+	uint32_t ks1[32], ks2[32];
+
+	if (!get_key(T1Key, ident, 'T', 1, 300, 1))
+	{
+		return 2;
+	}
+
+	if (!get_key(P1Key, ident, 'P', 1, 8, 1))
+	{
+		return 2;
+	}
+
+	if (!get_key(KeyDes, ident, '0', desKeyIndex, 16, 1))
+	{
+		return 2;
+	}
+
+	if (!get_key(XorKey, ident, 'X', 1, 8, 1))
+	{
+		return 2;
+	}
+
+	for (i = 0; i < 4; i++)
+	{
+		tmp[i] = data[i + 4];
+	}
+
+	via3_fct1(tmp, ident, XorKey, T1Key);
+
+	for (i = 0; i < 4; i++)
+	{
+		tmp[i] = data[i] ^ tmp[i + 4];
+	}
+
+	via3_fct2(tmp, ident, XorKey, T1Key);
+
+	for (i = 0; i < 4; i++)
+	{
+		tmp[i] ^= XorKey[i + 4];
+	}
+
+	for (i = 0; i < 4; i++)
+	{
+		data[i] = data[i + 4] ^ tmp[i + 4];
+		data[i + 4] = tmp[i];
+	}
+
+	des_set_key(KeyDes, ks1);
+	des_set_key(KeyDes + 8, ks2);
+
+	des(data, ks1, 0);
+	des(data, ks2, 1);
+	des(data, ks1, 0);
+
+	for (i = 0; i < 4; i++)
+	{
+		tmp[i] = data[i + 4];
+	}
+
+	via3_fct2(tmp, ident, XorKey, T1Key);
+
+	for (i = 0; i < 4; i++)
+	{
+		tmp[i] = data[i] ^ tmp[i + 4];
+	}
+
+	via3_fct1(tmp, ident, XorKey, T1Key);
+
+	for (i = 0; i < 4; i++)
+	{
+		tmp[i] ^= XorKey[i];
+	}
+
+	for (i = 0; i < 4; i++)
+	{
+		data[i] = data[i + 4] ^ tmp[i + 4];
+		data[i + 4] = tmp[i];
+	}
+
+	return 0;
+}
+
+static void via3_final_mix(uint8_t *dw)
+{
+	uint8_t tmp[4];
+
+	memcpy(tmp, dw, 4);
+	memcpy(dw, dw + 4, 4);
+	memcpy(dw + 4, tmp, 4);
+
+	memcpy(tmp, dw + 8, 4);
+	memcpy(dw + 8, dw + 12, 4);
+	memcpy(dw + 12, tmp, 4);
+}
+
+static int8_t via3_decrypt(uint8_t *source, uint8_t *dw, uint32_t ident, uint8_t desKeyIndex,
+							uint8_t aesKeyIndex, uint8_t aesMode, int8_t doFinalMix)
+{
+	int8_t aesAfterCore = 0, needsAES = (aesKeyIndex != 0xFF);
+	int32_t i, j;
+
+	uint8_t tmpData[8], C1[8];
+	uint8_t *pXorVector;
+
+	char aesKey[16];
+
+	if (ident == 0)
+	{
+		return EMU_CORRUPT_DATA;
+	}
+
+	if (!get_key(C1, ident, 'C', 1, 8, 1))
+	{
+		return EMU_KEY_NOT_FOUND;
+	}
+
+	if (needsAES && !get_key((uint8_t *)aesKey, ident, 'E', aesKeyIndex, 16, 1))
+	{
+		return EMU_KEY_NOT_FOUND;
+	}
+
+	if (aesMode == 0x0D || aesMode == 0x11 || aesMode == 0x15)
+	{
+		aesAfterCore = 1;
+	}
+
+	if (needsAES && !aesAfterCore)
+	{
+		if (aesMode == 0x0F)
+		{
+			hdSurEncPhase1_D2_0F_11(source);
+			hdSurEncPhase2_D2_0F_11(source);
+		}
+		else if (aesMode == 0x13)
+		{
+			hdSurEncPhase1_D2_13_15(source);
+		}
+
+		struct aes_keys aes;
+		aes_set_key(&aes, aesKey);
+		aes_decrypt(&aes, source, 16);
+
+		if (aesMode == 0x0F)
+		{
+			hdSurEncPhase1_D2_0F_11(source);
+		}
+		else if (aesMode == 0x13)
+		{
+			hdSurEncPhase2_D2_13_15(source);
+		}
+	}
+
+	for (i = 0; i < 2; i++)
+	{
+		memcpy(tmpData, source + i * 8, 8);
+		via3_process_dw(tmpData, ident, desKeyIndex);
+
+		if (i != 0)
+		{
+			pXorVector = source;
+		}
+		else
+		{
+			pXorVector = &C1[0];
+		}
+
+		for (j = 0; j < 8; j++)
+		{
+			dw[i * 8 + j] = tmpData[j] ^ pXorVector[j];
+		}
+	}
+
+	if (needsAES && aesAfterCore)
+	{
+		if (aesMode == 0x11)
+		{
+			hdSurEncPhase1_D2_0F_11(dw);
+			hdSurEncPhase2_D2_0F_11(dw);
+		}
+		else if (aesMode == 0x15)
+		{
+			hdSurEncPhase1_D2_13_15(dw);
+		}
+
+		struct aes_keys aes;
+		aes_set_key(&aes, aesKey);
+		aes_decrypt(&aes, dw, 16);
+
+		if (aesMode == 0x11)
+		{
+			hdSurEncPhase1_D2_0F_11(dw);
+		}
+
+		if (aesMode == 0x15)
+		{
+			hdSurEncPhase2_D2_13_15(dw);
+		}
+	}
+
+	if (ident == 0x030B00)
+	{
+		if (doFinalMix)
+		{
+			via3_final_mix(dw);
+		}
+
+		if (!is_valid_dcw(dw) || !is_valid_dcw(dw + 8))
+		{
+			return EMU_CHECKSUM_ERROR;
+		}
+	}
+
+	return EMU_OK;
+}
+
+int8_t viaccess_ecm(uint8_t *ecm, uint8_t *dw)
+{
+	int8_t doFinalMix = 0;
+
+	uint8_t nanoCmd = 0, nanoLen = 0, version = 0, providerKeyLen = 0;
+	uint8_t desKeyIndex = 0, aesMode = 0, aesKeyIndex = 0xFF;
+	uint16_t i = 0, keySelectPos = 0, ecmLen = SCT_LEN(ecm);
+	uint32_t currentIdent = 0;
+
+	for (i = 4; i + 2 < ecmLen; )
+	{
+		nanoCmd = ecm[i++];
+		nanoLen = ecm[i++];
+
+		if (i + nanoLen > ecmLen)
+		{
+			return EMU_NOT_SUPPORTED;
+		}
+
+		switch (nanoCmd)
+		{
+			case 0x40:
+				if (nanoLen < 0x03)
+				{
+					break;
+				}
+				version = ecm[i];
+				if (nanoLen == 3)
+				{
+					currentIdent = ((ecm[i] << 16) | (ecm[i + 1] << 8)) | (ecm[i + 2] & 0xF0);
+					desKeyIndex = ecm[i + 2] & 0x0F;
+					keySelectPos = i + 3;
+				}
+				else
+				{
+					currentIdent = (ecm[i] << 16) | (ecm[i + 1] << 8) | ((ecm[i + 2] >> 4) & 0x0F);
+					desKeyIndex = ecm[i + 3];
+					keySelectPos = i + 4;
+				}
+				providerKeyLen = nanoLen;
+				break;
+
+			case 0x90:
+				if (nanoLen < 0x03)
+				{
+					break;
+				}
+				version = ecm[i];
+				currentIdent = ((ecm[i] << 16) | (ecm[i + 1] << 8)) | (ecm[i + 2] & 0xF0);
+				desKeyIndex = ecm[i + 2] & 0x0F;
+				keySelectPos = i + 4;
+				if ((version == 3) && (nanoLen > 3))
+				{
+					desKeyIndex = ecm[i + (nanoLen - 4)] & 0x0F;
+				}
+				providerKeyLen = nanoLen;
+				break;
+
+			case 0x80:
+				nanoLen = 0;
+				break;
+
+			case 0xD2:
+				if (nanoLen < 0x02)
+				{
+					break;
+				}
+				aesMode = ecm[i];
+				aesKeyIndex = ecm[i + 1];
+				break;
+
+			case 0xDD:
+				nanoLen = 0;
+				break;
+
+			case 0xEA:
+				if (nanoLen < 0x10)
+				{
+					break;
+				}
+				if (version < 2)
+				{
+					return via1_decrypt(ecm, dw, currentIdent, desKeyIndex);
+				}
+				else if (version == 2)
+				{
+					return via26_decrypt(ecm + i, dw, currentIdent, desKeyIndex);
+				}
+				else if (version == 3)
+				{
+					doFinalMix = 0;
+					if (currentIdent == 0x030B00 && providerKeyLen > 3)
+					{
+						if (keySelectPos + 2 >= ecmLen)
+						{
+							break;
+						}
+						if (ecm[keySelectPos] == 0x05 && ecm[keySelectPos + 1] == 0x67 &&
+							(ecm[keySelectPos + 2] == 0x00 || ecm[keySelectPos + 2] == 0x01))
+						{
+							if (ecm[keySelectPos + 2] == 0x01)
+							{
+								doFinalMix = 1;
+							}
+						}
+						else
+						{
+							break;
+						}
+					}
+
+					return via3_decrypt(ecm + i, dw, currentIdent, desKeyIndex, aesKeyIndex, aesMode, doFinalMix);
+				}
+				break;
+
+			default:
+				break;
+		}
+
+		i += nanoLen;
+	}
+
+	return EMU_NOT_SUPPORTED;
+}
+
+// Viaccess EMM EMU
+
+int8_t viaccess_emm(uint8_t *emm, uint32_t *keysAdded)
+{
+	uint8_t nanoCmd = 0, subNanoCmd = 0, *tmp;
+	uint8_t ecmKeyCount = 0, emmKeyIndex = 0, aesMode = 0x0D;
+	uint8_t nanoLen = 0, subNanoLen = 0, haveEmmXorKey = 0, haveNewD0 = 0;
+	uint8_t ecmKeys[6][16], keyD0[2], emmKey[16], emmXorKey[16], provName[17];
+
+	uint16_t i = 0, j = 0, k = 0, emmLen = SCT_LEN(emm);
+	uint32_t ui1, ui2, ui3, ecmKeyIndex[6], provider = 0, ecmProvider = 0;
+
+	char keyName[EMU_MAX_CHAR_KEYNAME], keyValue[36];
+	struct aes_keys aes;
+
+	memset(keyD0, 0, 2);
+	memset(ecmKeyIndex, 0, sizeof(uint32_t) * 6);
+
+	for (i = 3; i + 2 < emmLen; )
+	{
+		nanoCmd = emm[i++];
+		nanoLen = emm[i++];
+
+		if (i + nanoLen > emmLen)
+		{
+			return EMU_NOT_SUPPORTED;
+		}
+
+		switch (nanoCmd)
+		{
+			case 0x90:
+			{
+				if (nanoLen < 3)
+				{
+					break;
+				}
+
+				ui1 = emm[i + 2];
+				ui2 = emm[i + 1];
+				ui3 = emm[i];
+				provider = (ui1 | (ui2 << 8) | (ui3 << 16));
+
+				if (provider == 0x00D00040)
+				{
+					ecmProvider = 0x030B00;
+				}
+				else
+				{
+					return EMU_NOT_SUPPORTED;
+				}
+				break;
+			}
+
+			case 0xD2:
+			{
+				if (nanoLen < 2)
+				{
+					break;
+				}
+
+				emmKeyIndex = emm[i + 1];
+				break;
+			}
+
+			case 0x41:
+			{
+				if (nanoLen < 1)
+				{
+					break;
+				}
+
+				if (!get_key(emmKey, provider, 'M', emmKeyIndex, 16, 1))
+				{
+					return EMU_KEY_NOT_FOUND;
+				}
+
+				memset(provName, 0, 17);
+				memset(emmXorKey, 0, 16);
+
+				k = nanoLen < 16 ? nanoLen : 16;
+
+				memcpy(provName, &emm[i], k);
+				aes_set_key(&aes, (char *)emmKey);
+				aes_decrypt(&aes, emmXorKey, 16);
+
+				for (j = 0; j < 16; j++)
+				{
+					provName[j] ^= emmXorKey[j];
+				}
+				provName[k] = 0;
+
+				if (strcmp((char *)provName, "TNTSAT") != 0 &&
+					strcmp((char *)provName, "TNTSATPRO") != 0 &&
+					strcmp((char *)provName, "CSAT V") != 0)
+				{
+					return EMU_NOT_SUPPORTED;
+				}
+
+				break;
+			}
+
+			case 0xBA:
+			{
+				if (nanoLen < 2)
+				{
+					break;
+				}
+
+				get_key(keyD0, ecmProvider, 'D', 0, 2, 0);
+
+				ui1 = (emm[i] << 8) | emm[i + 1];
+
+				if( (uint32_t)((keyD0[0] << 8) | keyD0[1]) < ui1 || (keyD0[0] == 0x00 && keyD0[1] == 0x00))
+				{
+					keyD0[0] = emm[i];
+					keyD0[1] = emm[i + 1];
+					haveNewD0 = 1;
+					break;
+				}
+
+				return EMU_OK;
+			}
+
+			case 0xBC:
+			{
+				break;
+			}
+
+			case 0x43:
+			{
+				if (nanoLen < 16)
+				{
+					break;
+				}
+
+				memcpy(emmXorKey, &emm[i], 16);
+				haveEmmXorKey = 1;
+
+				break;
+			}
+
+			case 0x44:
+			{
+				if (nanoLen < 3)
+				{
+					break;
+				}
+
+				if (!haveEmmXorKey)
+				{
+					memset(emmXorKey, 0, 16);
+				}
+
+				tmp = (uint8_t *)malloc(((nanoLen / 16) + 1) * 16 * sizeof(uint8_t));
+				if (tmp == NULL)
+				{
+					return EMU_OUT_OF_MEMORY;
+				}
+
+				memcpy(tmp, &emm[i], nanoLen);
+				aes_set_key(&aes, (char *)emmKey);
+
+				for (j = 0; j < nanoLen; j += 16)
+				{
+					aes_decrypt(&aes, emmXorKey, 16);
+
+					for (k = 0; k < 16; k++)
+					{
+						tmp[j + k] ^= emmXorKey[k];
+					}
+				}
+
+				memcpy(&emm[i - 2], tmp, nanoLen);
+				free(tmp);
+				nanoLen = 0;
+				i -= 2;
+				break;
+			}
+
+			case 0x68:
+			{
+				if (ecmKeyCount > 5)
+				{
+					break;
+				}
+
+				for (j = i; j + 2 < i + nanoLen; )
+				{
+					subNanoCmd = emm[j++];
+					subNanoLen = emm[j++];
+
+					if (j + subNanoLen > i + nanoLen)
+					{
+						break;
+					}
+
+					switch (subNanoCmd)
+					{
+						case 0xD2:
+						{
+							if (nanoLen < 2)
+							{
+								break;
+							}
+
+							aesMode = emm[j];
+							emmKeyIndex = emm[j + 1];
+							break;
+						}
+
+						case 0x01:
+						{
+							if(nanoLen < 17)
+							{
+								break;
+							}
+
+							ecmKeyIndex[ecmKeyCount] = emm[j];
+							memcpy(&ecmKeys[ecmKeyCount], &emm[j + 1], 16);
+
+							if (!get_key(emmKey, provider, 'M', emmKeyIndex, 16, 1))
+							{
+								break;
+							}
+
+							if (aesMode == 0x0F || aesMode == 0x11)
+							{
+								hdSurEncPhase1_D2_0F_11(ecmKeys[ecmKeyCount]);
+								hdSurEncPhase2_D2_0F_11(ecmKeys[ecmKeyCount]);
+							}
+							else if (aesMode == 0x13 || aesMode == 0x15)
+							{
+								hdSurEncPhase1_D2_13_15(ecmKeys[ecmKeyCount]);
+							}
+
+							aes_set_key(&aes, (char *)emmKey);
+							aes_decrypt(&aes, ecmKeys[ecmKeyCount], 16);
+
+							if (aesMode == 0x0F || aesMode == 0x11)
+							{
+								hdSurEncPhase1_D2_0F_11(ecmKeys[ecmKeyCount]);
+							}
+							else if (aesMode == 0x13 || aesMode == 0x15)
+							{
+								hdSurEncPhase2_D2_13_15(ecmKeys[ecmKeyCount]);
+							}
+
+							ecmKeyCount++;
+							break;
+						}
+
+						default:
+							break;
+					}
+
+					j += subNanoLen;
+				}
+				break;
+			}
+
+			case 0xF0:
+			{
+				if (nanoLen != 4)
+				{
+					break;
+				}
+
+				ui1 = ((emm[i + 2] << 8) | (emm[i + 1] << 16) | (emm[i] << 24) | emm[i + 3]);
+
+				if (ccitt32_crc(emm + 3, emmLen - 11) != ui1)
+				{
+					return EMU_CHECKSUM_ERROR;
+				}
+
+				if (haveNewD0)
+				{
+					SAFE_MUTEX_LOCK(&emu_key_data_mutex);
+					emu_set_key('V', ecmProvider, "D0", keyD0, 2, 1, NULL, NULL);
+
+					for (j = 0; j < ecmKeyCount; j++)
+					{
+						snprintf(keyName, EMU_MAX_CHAR_KEYNAME, "E%X", ecmKeyIndex[j]);
+						emu_set_key('V', ecmProvider, keyName, ecmKeys[j], 16, 1, NULL, NULL);
+
+						(*keysAdded)++;
+						cs_hexdump(0, ecmKeys[j], 16, keyValue, sizeof(keyValue));
+						cs_log("Key found in EMM: V %06X %s %s", ecmProvider, keyName, keyValue);
+					}
+					SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
+				}
+				break;
+			}
+
+			default:
+				break;
+		}
+
+		i += nanoLen;
+	}
+
+	return EMU_OK;
+}
+
+#endif // WITH_EMU
diff --git a/module-emulator-viaccess.h b/module-emulator-viaccess.h
new file mode 100644
index 00000000..6f4afcab
--- /dev/null
+++ b/module-emulator-viaccess.h
@@ -0,0 +1,11 @@
+#ifndef MODULE_EMULATOR_VIACCESS_H
+#define MODULE_EMULATOR_VIACCESS_H
+
+#ifdef WITH_EMU
+
+int8_t viaccess_ecm(uint8_t *ecm, uint8_t *dw);
+int8_t viaccess_emm(uint8_t *emm, uint32_t *keysAdded);
+
+#endif // WITH_EMU
+
+#endif // MODULE_EMULATOR_VIACCESS_H
diff --git a/module-emulator.c b/module-emulator.c
new file mode 100644
index 00000000..865e6687
--- /dev/null
+++ b/module-emulator.c
@@ -0,0 +1,894 @@
+#define MODULE_LOG_PREFIX "emu"
+
+#include "globals.h"
+
+#ifdef WITH_EMU
+
+#include "module-streamrelay.h"
+#include "module-emulator-osemu.h"
+#include "module-emulator-biss.h"
+#include "module-emulator-irdeto.h"
+#include "module-emulator-powervu.h"
+#include "oscam-conf-chk.h"
+#include "oscam-config.h"
+#include "oscam-reader.h"
+#include "oscam-string.h"
+
+/*
+ * Readers in OSCam consist of 2 basic parts.
+ * The hardware or the device part. This is where physical smart cards are inserted
+ * and made available to OSCam.
+ * The software or the emulation part. This is where the actual card reading is done,
+ * including ecm and emm processing (i.e emulation of the various cryptosystems).
+ * In the Emu reader, the device part has no meaning, but we have to create it in
+ * order to be compatible with OSCam's reader structure.
+*/
+
+/*
+ * Create the Emu "emulation" part. This is of type s_cardsystem.
+ * Similar structures are found in the main sources folder (files reader-xxxxxx.c)
+ * for every cryptosystem supported by OSCam.
+ * Here we read keys from our virtual card (aka the SoftCam.Key file) and we inform
+ * OSCam about them. This is done with the emu_card_info() function. Keep in mind
+ * that Emu holds all its keys to separate structures for faster access.
+ * In addition, ECM and EMM requests are processed here, with the emu_do_ecm() and
+ * emu_do_emm() functions.
+*/
+
+#define CS_OK    1
+#define CS_ERROR 0
+
+extern char cs_confdir[128];
+#ifdef MODULE_STREAMRELAY
+static int8_t emu_key_data_mutex_init = 0;
+#endif
+pthread_mutex_t emu_key_data_mutex;
+
+static void set_hexserial_to_version(struct s_reader *rdr)
+{
+	char cVersion[32];
+	uint32_t version = EMU_VERSION;
+	uint8_t hversion[2];
+	memset(hversion, 0, 2);
+	snprintf(cVersion, sizeof(cVersion), "%04d", version);
+	char_to_bin(hversion, cVersion, 4);
+	rdr->hexserial[3] = hversion[0];
+	rdr->hexserial[4] = hversion[1];
+}
+
+static void set_prids(struct s_reader *rdr)
+{
+	int32_t i, j;
+
+	rdr->nprov = 0;
+
+	for (i = 0; (i < rdr->emu_auproviders.nfilts) && (rdr->nprov < CS_MAXPROV); i++)
+	{
+		for (j = 0; (j < rdr->emu_auproviders.filts[i].nprids) && (rdr->nprov < CS_MAXPROV); j++)
+		{
+			i2b_buf(4, rdr->emu_auproviders.filts[i].prids[j], rdr->prid[i]);
+			rdr->nprov++;
+		}
+	}
+}
+
+static void emu_add_entitlement(struct s_reader *rdr, uint16_t caid, uint32_t provid, uint8_t *key, char *keyName, uint32_t keyLength, uint8_t isData)
+{
+	if (!rdr->ll_entitlements)
+	{
+		rdr->ll_entitlements = ll_create("ll_entitlements");
+	}
+
+	S_ENTITLEMENT *item;
+	if (cs_malloc(&item, sizeof(S_ENTITLEMENT)))
+	{
+		// fill item
+		item->caid = caid;
+		item->provid = provid;
+		item->id = 0;
+		item->class = 0;
+		item->start = 0;
+		item->end = 2147472000;
+		item->type = 0;
+		item->isKey = 1;
+		memcpy(item->name, keyName, 8);
+		item->key = key;
+		item->keyLength = keyLength;
+		item->isData = isData;
+
+		// add item
+		ll_append(rdr->ll_entitlements, item);
+	}
+}
+
+static void refresh_entitlements(struct s_reader *rdr)
+{
+	uint32_t i;
+	uint16_t caid;
+	KeyData *tmpKeyData;
+	LL_ITER itr;
+	biss2_rsa_key_t *item;
+
+	cs_clear_entitlement(rdr);
+
+	for (i = 0; i < StreamKeys.keyCount; i++)
+	{
+		emu_add_entitlement(rdr, b2i(2, StreamKeys.EmuKeys[i].key), StreamKeys.EmuKeys[i].provider, StreamKeys.EmuKeys[i].key,
+							StreamKeys.EmuKeys[i].keyName, StreamKeys.EmuKeys[i].keyLength, 1);
+	}
+
+	for (i = 0; i < ViKeys.keyCount; i++)
+	{
+		emu_add_entitlement(rdr, 0x0500, ViKeys.EmuKeys[i].provider, ViKeys.EmuKeys[i].key,
+							ViKeys.EmuKeys[i].keyName, ViKeys.EmuKeys[i].keyLength, 0);
+	}
+
+	for (i = 0; i < IrdetoKeys.keyCount; i++)
+	{
+		tmpKeyData = &IrdetoKeys.EmuKeys[i];
+		do
+		{
+			emu_add_entitlement(rdr, tmpKeyData->provider >> 8, tmpKeyData->provider & 0xFF,
+								tmpKeyData->key, tmpKeyData->keyName, tmpKeyData->keyLength, 0);
+
+			tmpKeyData = tmpKeyData->nextKey;
+		}
+		while (tmpKeyData != NULL);
+	}
+
+	for (i = 0; i < CwKeys.keyCount; i++)
+	{
+		emu_add_entitlement(rdr, CwKeys.EmuKeys[i].provider >> 8, CwKeys.EmuKeys[i].provider & 0xFF,
+							CwKeys.EmuKeys[i].key, CwKeys.EmuKeys[i].keyName, CwKeys.EmuKeys[i].keyLength, 0);
+	}
+
+	for (i = 0; i < PowervuKeys.keyCount; i++)
+	{
+		emu_add_entitlement(rdr, 0x0E00, PowervuKeys.EmuKeys[i].provider, PowervuKeys.EmuKeys[i].key,
+							PowervuKeys.EmuKeys[i].keyName, PowervuKeys.EmuKeys[i].keyLength, 0);
+	}
+
+	for (i = 0; i < TandbergKeys.keyCount; i++)
+	{
+		emu_add_entitlement(rdr, 0x1010, TandbergKeys.EmuKeys[i].provider, TandbergKeys.EmuKeys[i].key,
+							TandbergKeys.EmuKeys[i].keyName, TandbergKeys.EmuKeys[i].keyLength, 0);
+	}
+
+	for (i = 0; i < NagraKeys.keyCount; i++)
+	{
+		emu_add_entitlement(rdr, 0x1801, NagraKeys.EmuKeys[i].provider, NagraKeys.EmuKeys[i].key,
+							NagraKeys.EmuKeys[i].keyName, NagraKeys.EmuKeys[i].keyLength, 0);
+	}
+
+	// Session words for BISS1 mode 1/E (caid 2600) and BISS2 mode 1/E (caid 2602)
+	for (i = 0; i < BissSWs.keyCount; i++)
+	{
+		caid = (BissSWs.EmuKeys[i].keyLength == 8) ? 0x2600 : 0x2602;
+		emu_add_entitlement(rdr, caid, BissSWs.EmuKeys[i].provider, BissSWs.EmuKeys[i].key,
+							BissSWs.EmuKeys[i].keyName, BissSWs.EmuKeys[i].keyLength, 0);
+	}
+
+	// Session keys (ECM keys) for BISS2 mode CA
+	for (i = 0; i < Biss2Keys.keyCount; i++)
+	{
+		emu_add_entitlement(rdr, 0x2610, Biss2Keys.EmuKeys[i].provider, Biss2Keys.EmuKeys[i].key,
+							Biss2Keys.EmuKeys[i].keyName, Biss2Keys.EmuKeys[i].keyLength, 0);
+	}
+
+	// RSA keys (EMM keys) for BISS2 mode CA
+	itr = ll_iter_create(rdr->ll_biss2_rsa_keys);
+	while ((item = ll_iter_next(&itr)))
+	{
+		emu_add_entitlement(rdr, 0x2610, 0, item->ekid, "RSAPRI", 8, 0);
+	}
+
+	for (i = 0; i < OmnicryptKeys.keyCount; i++)
+	{
+		emu_add_entitlement(rdr, 0x00FF, OmnicryptKeys.EmuKeys[i].provider, OmnicryptKeys.EmuKeys[i].key,
+							OmnicryptKeys.EmuKeys[i].keyName, OmnicryptKeys.EmuKeys[i].keyLength, 0);
+	}
+}
+
+static int32_t emu_do_ecm(struct s_reader *rdr, const ECM_REQUEST *er, struct s_ecm_answer *ea)
+{
+	if (!emu_process_ecm(rdr, er, ea->cw, &ea->cw_ex))
+	{
+		return CS_OK;
+	}
+
+	return CS_ERROR;
+}
+
+static int32_t emu_do_emm(struct s_reader *rdr, EMM_PACKET *emm)
+{
+	uint32_t keysAdded = 0;
+
+	if (emm->emmlen < 3)
+	{
+		return CS_ERROR;
+	}
+
+	if (SCT_LEN(emm->emm) > emm->emmlen)
+	{
+		return CS_ERROR;
+	}
+
+	if (!emu_process_emm(rdr, b2i(2, emm->caid), emm->emm, &keysAdded))
+	{
+		if (keysAdded > 0)
+		{
+			refresh_entitlements(rdr);
+		}
+
+		return CS_OK;
+	}
+
+	return CS_ERROR;
+}
+
+static int32_t emu_card_info(struct s_reader *rdr)
+{
+	SAFE_MUTEX_LOCK(&emu_key_data_mutex);
+
+	// Delete keys from Emu's memory
+	emu_clear_keydata();
+
+	// Delete BISS2 mode CA RSA keys
+	ll_destroy_data(&rdr->ll_biss2_rsa_keys);
+
+	// Read keys built in the OSCam-Emu binary
+	emu_read_keymemory(rdr);
+
+	// Read keys from SoftCam.Key file
+	emu_set_keyfile_path(cs_confdir);
+
+	if (!emu_read_keyfile(rdr, cs_confdir))
+	{
+		if (emu_read_keyfile(rdr, "/var/keys/"))
+		{
+			emu_set_keyfile_path("/var/keys/");
+		}
+	}
+
+	// Read BISS2 mode CA RSA keys from PEM files
+	biss_read_pem(rdr, BISS2_MAX_RSA_KEYS);
+
+	cs_log("Total keys in memory: W:%d V:%d N:%d I:%d F:%d G:%d O:%d P:%d T:%d A:%d",
+			CwKeys.keyCount, ViKeys.keyCount, NagraKeys.keyCount, IrdetoKeys.keyCount, BissSWs.keyCount,
+			Biss2Keys.keyCount, OmnicryptKeys.keyCount, PowervuKeys.keyCount, TandbergKeys.keyCount,
+			StreamKeys.keyCount);
+
+	// Inform OSCam about all available keys.
+	// This is used for listing the "entitlements" in the webif's reader page.
+	refresh_entitlements(rdr);
+
+	SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
+
+	set_prids(rdr);
+
+	set_hexserial_to_version(rdr);
+
+	return CS_OK;
+}
+
+/*
+static int32_t emu_card_init(struct s_reader *UNUSED(rdr), struct s_ATR *UNUSED(atr))
+{
+	return CS_ERROR;
+}
+*/
+
+int32_t emu_get_via3_emm_type(EMM_PACKET *ep, struct s_reader *rdr)
+{
+	uint32_t provid = 0;
+
+	if(ep->emm[3] == 0x90 && ep->emm[4] == 0x03)
+	{
+		provid = b2i(3, ep->emm + 5);
+		provid &= 0xFFFFF0;
+		i2b_buf(4, provid, ep->provid);
+	}
+
+	switch (ep->emm[0])
+	{
+		case 0x88:
+			ep->type = UNIQUE;
+			memset(ep->hexserial, 0, 8);
+			memcpy(ep->hexserial, ep->emm + 4, 4);
+			rdr_log_dbg(rdr, D_EMM, "UNIQUE");
+			return 1;
+
+		case 0x8A:
+		case 0x8B:
+			ep->type = GLOBAL;
+			rdr_log_dbg(rdr, D_EMM, "GLOBAL");
+			return 1;
+
+		case 0x8C:
+		case 0x8D:
+			ep->type = SHARED;
+			rdr_log_dbg(rdr, D_EMM, "SHARED (part)");
+			// We need those packets to pass otherwise we would never
+			// be able to complete EMM reassembly
+			return 1;
+
+		case 0x8E:
+			ep->type = SHARED;
+			rdr_log_dbg(rdr, D_EMM, "SHARED");
+			memset(ep->hexserial, 0, 8);
+			memcpy(ep->hexserial, ep->emm + 3, 3);
+			return 1;
+
+		default:
+			ep->type = UNKNOWN;
+			rdr_log_dbg(rdr, D_EMM, "UNKNOWN");
+			return 1;
+	}
+}
+
+int32_t emu_get_ird2_emm_type(EMM_PACKET *ep, struct s_reader *rdr)
+{
+	int32_t l = (ep->emm[3] & 0x07);
+	int32_t base = (ep->emm[3] >> 3);
+	char dumprdrserial[l * 3], dumpemmserial[l * 3];
+
+	switch (l)
+	{
+		case 0:
+			// global emm, 0 bytes addressed
+			ep->type = GLOBAL;
+			rdr_log_dbg(rdr, D_EMM, "GLOBAL base = %02x", base);
+			return 1;
+
+		case 2:
+			// shared emm, 2 bytes addressed
+			ep->type = SHARED;
+			memset(ep->hexserial, 0, 8);
+			memcpy(ep->hexserial, ep->emm + 4, l);
+			cs_hexdump(1, rdr->hexserial, l, dumprdrserial, sizeof(dumprdrserial));
+			cs_hexdump(1, ep->hexserial, l, dumpemmserial, sizeof(dumpemmserial));
+			rdr_log_dbg_sensitive(rdr, D_EMM, "SHARED l = %d ep = {%s} rdr = {%s} base = %02x",
+									l, dumpemmserial, dumprdrserial, base);
+			return 1;
+
+		case 3:
+			// unique emm, 3 bytes addressed
+			ep->type = UNIQUE;
+			memset(ep->hexserial, 0, 8);
+			memcpy(ep->hexserial, ep->emm + 4, l);
+			cs_hexdump(1, rdr->hexserial, l, dumprdrserial, sizeof(dumprdrserial));
+			cs_hexdump(1, ep->hexserial, l, dumpemmserial, sizeof(dumpemmserial));
+			rdr_log_dbg_sensitive(rdr, D_EMM, "UNIQUE l = %d ep = {%s} rdr = {%s} base = %02x",
+									l, dumpemmserial, dumprdrserial, base);
+			return 1;
+
+		default:
+			ep->type = UNKNOWN;
+			rdr_log_dbg(rdr, D_EMM, "UNKNOWN");
+			return 1;
+	}
+}
+
+int32_t emu_get_pvu_emm_type(EMM_PACKET *ep, struct s_reader *rdr)
+{
+	if (ep->emm[0] == 0x82)
+	{
+		ep->type = UNIQUE;
+		memset(ep->hexserial, 0, 8);
+		memcpy(ep->hexserial, ep->emm + 12, 4);
+	}
+	else
+	{
+		ep->type = UNKNOWN;
+		rdr_log_dbg(rdr, D_EMM, "UNKNOWN");
+	}
+	return 1;
+}
+
+int32_t emu_get_tan_emm_type(EMM_PACKET *ep, struct s_reader *rdr)
+{
+	if (ep->emm[0] == 0x82 || ep->emm[0] == 0x83)
+	{
+		ep->type = GLOBAL;
+	}
+	else
+	{
+		ep->type = UNKNOWN;
+		rdr_log_dbg(rdr, D_EMM, "UNKNOWN");
+	}
+	return 1;
+}
+
+int32_t emu_get_biss_emm_type(EMM_PACKET *ep, struct s_reader *rdr)
+{
+	switch (ep->emm[0])
+	{
+		case 0x81: // Spec say this is for EMM, but oscam (and all other crypto systems) use it for ECM
+		case 0x82:
+		case 0x83:
+		case 0x84:
+		case 0x85:
+		case 0x86:
+		case 0x87:
+		case 0x88:
+		case 0x89:
+		case 0x8A:
+		case 0x8B:
+		case 0x8C:
+		case 0x8D:
+		case 0x8E:
+		case 0x8F:
+			ep->type = GLOBAL;
+			return 1;
+
+		default:
+			ep->type = UNKNOWN;
+			rdr_log_dbg(rdr, D_EMM, "UNKNOWN");
+			return 1;
+	}
+}
+
+static int32_t emu_get_emm_type(struct emm_packet_t *ep, struct s_reader *rdr)
+{
+	uint16_t caid = b2i(2, ep->caid);
+
+	if (caid_is_viaccess(caid))     return emu_get_via3_emm_type(ep, rdr);
+	if (caid_is_irdeto(caid))       return emu_get_ird2_emm_type(ep, rdr);
+	if (caid_is_powervu(caid))      return emu_get_pvu_emm_type(ep, rdr);
+	if (caid_is_director(caid))     return emu_get_tan_emm_type(ep, rdr);
+	if (caid_is_biss_dynamic(caid)) return emu_get_biss_emm_type(ep, rdr);
+
+	return CS_ERROR;
+}
+
+FILTER *get_emu_prids_for_caid(struct s_reader *rdr, uint16_t caid)
+{
+	int32_t i;
+
+	for (i = 0; i < rdr->emu_auproviders.nfilts; i++)
+	{
+		if (caid == rdr->emu_auproviders.filts[i].caid)
+		{
+			return &rdr->emu_auproviders.filts[i];
+		}
+	}
+
+	return NULL;
+}
+
+static int32_t emu_get_via3_emm_filter(struct s_reader *UNUSED(rdr), struct s_csystem_emm_filter **emm_filters, unsigned int *filter_count, uint16_t UNUSED(caid), uint32_t UNUSED(provid))
+{
+	if (*emm_filters == NULL)
+	{
+		const unsigned int max_filter_count = 1;
+		if (!cs_malloc(emm_filters, max_filter_count * sizeof(struct s_csystem_emm_filter)))
+		{
+			return CS_ERROR;
+		}
+
+		struct s_csystem_emm_filter *filters = *emm_filters;
+		*filter_count = 0;
+
+		int32_t idx = 0;
+
+		filters[idx].type = EMM_GLOBAL;
+		filters[idx].enabled   = 1;
+		filters[idx].filter[0] = 0x8A;
+		filters[idx].mask[0]   = 0xFE;
+		filters[idx].filter[3] = 0x80;
+		filters[idx].mask[3]   = 0x80;
+		idx++;
+
+		*filter_count = idx;
+	}
+
+	return CS_OK;
+}
+
+static int32_t emu_get_ird2_emm_filter(struct s_reader *rdr, struct s_csystem_emm_filter **emm_filters, unsigned int *filter_count, uint16_t caid, uint32_t UNUSED(provid))
+{
+	uint8_t hexserial[3], prid[4];
+	FILTER *emu_provids;
+	int8_t have_provid = 0, have_serial = 0;
+	int32_t i;
+
+	SAFE_MUTEX_LOCK(&emu_key_data_mutex);
+	if(irdeto2_get_hexserial(caid, hexserial))
+	{
+		have_serial = 1;
+	}
+	SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
+
+	emu_provids = get_emu_prids_for_caid(rdr, caid);
+	if (emu_provids != NULL && emu_provids->nprids > 0)
+	{
+		have_provid = 1;
+	}
+
+	if (*emm_filters == NULL)
+	{
+		const unsigned int max_filter_count = have_serial + (2 * (have_provid ? emu_provids->nprids : 0));
+		if (!cs_malloc(emm_filters, max_filter_count * sizeof(struct s_csystem_emm_filter)))
+		{
+			return CS_ERROR;
+		}
+
+		struct s_csystem_emm_filter *filters = *emm_filters;
+		*filter_count = 0;
+
+		unsigned int idx = 0;
+
+		if (have_serial)
+		{
+			filters[idx].type = EMM_UNIQUE;
+			filters[idx].enabled   = 1;
+			filters[idx].filter[0] = 0x82;
+			filters[idx].mask[0]   = 0xFF;
+			filters[idx].filter[1] = 0xFB;
+			filters[idx].mask[1]   = 0x07;
+			memcpy(&filters[idx].filter[2], hexserial, 3);
+			memset(&filters[idx].mask[2], 0xFF, 3);
+			idx++;
+		}
+
+		for (i = 0; have_provid && i < emu_provids->nprids; i++)
+		{
+			i2b_buf(4, emu_provids->prids[i], prid);
+
+			filters[idx].type = EMM_UNIQUE;
+			filters[idx].enabled   = 1;
+			filters[idx].filter[0] = 0x82;
+			filters[idx].mask[0]   = 0xFF;
+			filters[idx].filter[1] = 0xFB;
+			filters[idx].mask[1]   = 0x07;
+			memcpy(&filters[idx].filter[2], &prid[1], 3);
+			memset(&filters[idx].mask[2], 0xFF, 3);
+			idx++;
+
+			filters[idx].type = EMM_SHARED;
+			filters[idx].enabled   = 1;
+			filters[idx].filter[0] = 0x82;
+			filters[idx].mask[0]   = 0xFF;
+			filters[idx].filter[1] = 0xFA;
+			filters[idx].mask[1]   = 0x07;
+			memcpy(&filters[idx].filter[2], &prid[1], 2);
+			memset(&filters[idx].mask[2], 0xFF, 2);
+			idx++;
+		}
+
+		*filter_count = idx;
+	}
+
+	return CS_OK;
+}
+
+static int32_t emu_get_pvu_emm_filter(struct s_csystem_emm_filter **emm_filters, unsigned int *filter_count,
+										uint16_t caid, uint16_t srvid, uint16_t tsid, uint16_t onid, uint32_t ens)
+{
+	uint8_t hexserials[32][4];
+	uint32_t i, count = 0;
+
+	SAFE_MUTEX_LOCK(&emu_key_data_mutex);
+	count = powervu_get_hexserials_new(hexserials, 32, caid, tsid, onid, ens);
+	if (count == 0)
+	{
+		count = powervu_get_hexserials(hexserials, 32, srvid);
+		if (count == 0)
+		{
+			SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
+			return CS_ERROR;
+		}
+	}
+	SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
+
+	if (*emm_filters == NULL)
+	{
+		const unsigned int max_filter_count = count;
+		if (!cs_malloc(emm_filters, max_filter_count * sizeof(struct s_csystem_emm_filter)))
+		{
+			return CS_ERROR;
+		}
+
+		struct s_csystem_emm_filter *filters = *emm_filters;
+		*filter_count = 0;
+
+		int32_t idx = 0;
+
+		for (i = 0; i < count; i++)
+		{
+			filters[idx].type = EMM_UNIQUE;
+			filters[idx].enabled    = 1;
+			filters[idx].filter[0]  = 0x82;
+			filters[idx].filter[10] = hexserials[i][0];
+			filters[idx].filter[11] = hexserials[i][1];
+			filters[idx].filter[12] = hexserials[i][2];
+			filters[idx].filter[13] = hexserials[i][3];
+			filters[idx].mask[0]    = 0xFF;
+			filters[idx].mask[10]   = 0xFF;
+			filters[idx].mask[11]   = 0xFF;
+			filters[idx].mask[12]   = 0xFF;
+			filters[idx].mask[13]   = 0xFF;
+			idx++;
+		}
+
+		*filter_count = idx;
+	}
+
+	return CS_OK;
+}
+
+static int32_t emu_get_tan_emm_filter(struct s_reader *UNUSED(rdr), struct s_csystem_emm_filter **emm_filters, unsigned int *filter_count, uint16_t UNUSED(caid), uint32_t UNUSED(provid))
+{
+	if (*emm_filters == NULL)
+	{
+		const unsigned int max_filter_count = 2;
+		uint8_t buf[8];
+
+		if (!emu_find_key('T', 0x40, 0, "MK", buf, 8, 0, 0, 0, NULL) &&
+			!emu_find_key('T', 0x40, 0, "MK01", buf, 8, 0, 0, 0, NULL))
+		{
+			return CS_ERROR;
+		}
+
+		if (!cs_malloc(emm_filters, max_filter_count * sizeof(struct s_csystem_emm_filter)))
+		{
+			return CS_ERROR;
+		}
+
+		struct s_csystem_emm_filter *filters = *emm_filters;
+		*filter_count = 0;
+
+		int32_t idx = 0;
+
+		filters[idx].type = EMM_GLOBAL;
+		filters[idx].enabled   = 1;
+		filters[idx].filter[0] = 0x82;
+		filters[idx].mask[0]   = 0xFF;
+		idx++;
+
+		filters[idx].type = EMM_GLOBAL;
+		filters[idx].enabled   = 1;
+		filters[idx].filter[0] = 0x83;
+		filters[idx].mask[0]   = 0xFF;
+		idx++;
+
+		*filter_count = idx;
+	}
+
+	return CS_OK;
+}
+
+static int32_t emu_get_biss_emm_filter(struct s_reader *UNUSED(rdr), struct s_csystem_emm_filter **emm_filters, unsigned int *filter_count, uint16_t UNUSED(caid), uint32_t UNUSED(provid))
+{
+	if (*emm_filters == NULL)
+	{
+		const unsigned int max_filter_count = 15;
+		if (!cs_malloc(emm_filters, max_filter_count * sizeof(struct s_csystem_emm_filter)))
+		{
+			return CS_ERROR;
+		}
+
+		struct s_csystem_emm_filter *filters = *emm_filters;
+		*filter_count = 0;
+
+		int32_t idx = 0;
+		uint8_t i;
+
+		for (i = 0; i < max_filter_count; i++)
+		{
+			filters[idx].type = EMM_GLOBAL;
+			filters[idx].enabled   = 1;
+			filters[idx].filter[0] = 0x81 + i; // What about table 0x81?
+			filters[idx].mask[0]   = 0xFF;
+			idx++;
+
+			*filter_count = idx;
+		}
+	}
+	return CS_OK;
+}
+
+static int32_t emu_get_emm_filter(struct s_reader *UNUSED(rdr), struct s_csystem_emm_filter **UNUSED(emm_filters), unsigned int *UNUSED(filter_count))
+{
+	return CS_ERROR;
+}
+
+static int32_t emu_get_emm_filter_adv(struct s_reader *rdr, struct s_csystem_emm_filter **emm_filters, unsigned int *filter_count,
+										uint16_t caid, uint32_t provid, uint16_t srvid, uint16_t tsid, uint16_t onid, uint32_t ens)
+{
+	if (caid_is_viaccess(caid))     return emu_get_via3_emm_filter(rdr, emm_filters, filter_count, caid, provid);
+	if (caid_is_irdeto(caid))       return emu_get_ird2_emm_filter(rdr, emm_filters, filter_count, caid, provid);
+	if (caid_is_powervu(caid))      return emu_get_pvu_emm_filter(emm_filters, filter_count, caid, srvid, tsid, onid, ens);
+	if (caid_is_director(caid))     return emu_get_tan_emm_filter(rdr, emm_filters, filter_count, caid, provid);
+	if (caid_is_biss_dynamic(caid)) return emu_get_biss_emm_filter(rdr, emm_filters, filter_count, caid, provid);
+
+	return CS_ERROR;
+}
+
+const struct s_cardsystem reader_emu =
+{
+	.desc = "emu",
+	.caids = (uint16_t[]){ 0x05, 0x06, 0x0D, 0x0E, 0x10, 0x18, 0x26, 0 },
+	.do_ecm = emu_do_ecm,
+	.do_emm = emu_do_emm,
+	.card_info = emu_card_info,
+	//.card_init = emu_card_init, // apparently this is not needed at all
+	.get_emm_type = emu_get_emm_type,
+	.get_emm_filter = emu_get_emm_filter, // needed to pass checks
+	.get_emm_filter_adv = emu_get_emm_filter_adv,
+};
+
+/*
+ * Create the Emu virtual "device" part. This is of type s_cardreader.
+ * Similar structures are found in the csctapi (Card System Card Terminal API)
+ * folder for every IFD (InterFace Device), aka smart card reader.
+ * Since we have no hardware to initialize, we start our Stream Relay server
+ * with the emu_reader_init() function.
+ * At Emu shutdown, we remove keys from memory with the emu_close() function.
+*/
+
+#define CR_OK    0
+#define CR_ERROR 1
+
+static int32_t emu_reader_init(struct s_reader *UNUSED(reader))
+{
+#ifdef MODULE_STREAMRELAY
+	if (cfg.stream_relay_enabled && (stream_server_thread_init == 0))
+	{
+		int32_t i;
+		stream_server_thread_init = 1;
+		SAFE_MUTEX_INIT(&emu_fixed_key_srvid_mutex, NULL);
+
+		for (i = 0; i < EMU_STREAM_SERVER_MAX_CONNECTIONS; i++)
+		{
+			SAFE_MUTEX_INIT(&emu_fixed_key_data_mutex[i], NULL);
+			ll_emu_stream_delayed_keys[i] = ll_create("ll_emu_stream_delayed_keys");
+			memset(&emu_fixed_key_data[i], 0, sizeof(emu_stream_client_key_data));
+		}
+
+		start_thread("stream_key_delayer", stream_key_delayer, NULL, NULL, 1, 1);
+		cs_log("Stream key delayer initialized");
+	}
+
+	// Initialize mutex for exclusive access to key database and key file
+	if (!emu_key_data_mutex_init)
+	{
+		SAFE_MUTEX_INIT(&emu_key_data_mutex, NULL);
+		emu_key_data_mutex_init = 1;
+	}
+#endif
+	return CR_OK;
+}
+
+static int32_t emu_close(struct s_reader *UNUSED(reader))
+{
+	cs_log("Reader is shutting down");
+
+	// Delete keys from Emu's memory
+	SAFE_MUTEX_LOCK(&emu_key_data_mutex);
+	emu_clear_keydata();
+	SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
+
+	return CR_OK;
+}
+
+static int32_t emu_get_status(struct s_reader *UNUSED(reader), int32_t *in) { *in = 1; return CR_OK; }
+static int32_t emu_activate(struct s_reader *UNUSED(reader), struct s_ATR *UNUSED(atr)) { return CR_OK; }
+static int32_t emu_transmit(struct s_reader *UNUSED(reader), uint8_t *UNUSED(buffer), uint32_t UNUSED(size), uint32_t UNUSED(expectedlen), uint32_t UNUSED(delay), uint32_t UNUSED(timeout)) { return CR_OK; }
+static int32_t emu_receive(struct s_reader *UNUSED(reader), uint8_t *UNUSED(buffer), uint32_t UNUSED(size), uint32_t UNUSED(delay), uint32_t UNUSED(timeout)) { return CR_OK; }
+static int32_t emu_write_settings(struct s_reader *UNUSED(reader), struct s_cardreader_settings *UNUSED(s)) { return CR_OK; }
+static int32_t emu_card_write(struct s_reader *UNUSED(pcsc_reader), const uint8_t *UNUSED(buf), uint8_t *UNUSED(cta_res), uint16_t *UNUSED(cta_lr), int32_t UNUSED(l)) { return CR_OK; }
+static int32_t emu_set_protocol(struct s_reader *UNUSED(rdr), uint8_t *UNUSED(params), uint32_t *UNUSED(length), uint32_t UNUSED(len_request)) { return CR_OK; }
+
+const struct s_cardreader cardreader_emu =
+{
+	.desc                   = "emu",
+	.typ                    = R_EMU,
+	.skip_extra_atr_parsing = 1,
+	.reader_init            = emu_reader_init,
+	.get_status             = emu_get_status,
+	.activate               = emu_activate,
+	.transmit               = emu_transmit,
+	.receive                = emu_receive,
+	.close                  = emu_close,
+	.write_settings         = emu_write_settings,
+	.card_write             = emu_card_write,
+	.set_protocol           = emu_set_protocol,
+};
+
+void add_emu_reader(void)
+{
+	// This function is called inside oscam.c and creates an emu [reader] with default
+	// settings in oscam.server file. If an emu [reader] already exists, it uses that.
+
+	LL_ITER itr;
+	struct s_reader *rdr;
+	int8_t haveEmuReader = 0;
+	char emuName[] = "emulator";
+	char *ctab, *ftab, *emu_auproviders, *disablecrccws_only_for;
+
+	// Check if emu [reader] entry already exists in oscam.server file and get it
+	itr = ll_iter_create(configured_readers);
+	while ((rdr = ll_iter_next(&itr)))
+	{
+		if (rdr->typ == R_EMU)
+		{
+			haveEmuReader = 1;
+			break;
+		}
+	}
+
+	rdr = NULL;
+
+	// If there's no emu [reader] in oscam.server, create one with default settings
+	if (!haveEmuReader)
+	{
+		if (!cs_malloc(&rdr, sizeof(struct s_reader)))
+		{
+			return;
+		}
+
+		reader_set_defaults(rdr);
+
+		rdr->enable = 1;
+		rdr->typ = R_EMU;
+		cs_strncpy(rdr->label, emuName, sizeof(emuName));
+		cs_strncpy(rdr->device, emuName, sizeof(emuName));
+
+		// CAIDs
+		ctab = strdup("0500,0604,0D00,0E00,1010,1801,2600,2602,2610");
+		chk_caidtab(ctab, &rdr->ctab);
+		NULLFREE(ctab);
+
+		// Idents
+		ftab = strdup("0500:020A00,021110;"
+					  "0604:000000;"
+					  "0D00:0000C0;"
+					  "0E00:000000;"
+					  "1010:000000;"
+					  "1801:000000,001101,002111,007301;"
+					  "2600:000000;"
+					  "2602:000000;"
+					  "2610:000000;"
+					 );
+		chk_ftab(ftab, &rdr->ftab);
+		NULLFREE(ftab);
+
+		// AU providers
+		emu_auproviders = strdup("0604:010200;0E00:000000;1010:000000;2610:000000;");
+		chk_ftab(emu_auproviders, &rdr->emu_auproviders);
+		NULLFREE(emu_auproviders);
+
+		// EMM cache
+		rdr->cachemm = 2;
+		rdr->rewritemm = 1;
+		rdr->logemm = 2;
+		rdr->deviceemm = 1;
+
+		// User group
+		rdr->grp = 0x1ULL;
+
+		// Add the "device" part to our emu reader
+		rdr->crdr = &cardreader_emu;
+
+		// Disable CW checksum test for PowerVu
+		disablecrccws_only_for = strdup("0E00:000000");
+		chk_ftab(disablecrccws_only_for, &rdr->disablecrccws_only_for);
+		NULLFREE(disablecrccws_only_for);
+
+		reader_fixups_fn(rdr);
+		ll_append(configured_readers, rdr);
+	}
+
+	// Set DVB Api delayer option
+#ifdef HAVE_DVBAPI
+	if (cfg.dvbapi_enabled && cfg.dvbapi_delayer < 60)
+	{
+		cfg.dvbapi_delayer = 60;
+	}
+#endif
+
+	cs_log("OSCam-Emu version %d", EMU_VERSION);
+}
+
+#endif // WITH_EMU
diff --git a/module-newcamd.c b/module-newcamd.c
index fc3f83cf..7c23924c 100644
--- a/module-newcamd.c
+++ b/module-newcamd.c
@@ -1118,6 +1118,13 @@ static int8_t newcamd_auth_client(IN_ADDR_T ip, uint8_t *deskey)
 			// set userfilter for au enabled clients
 			if(aureader)
 			{
+#ifdef WITH_EMU
+				if(aureader->typ == R_EMU)
+				{
+					usr_filter = *get_emu_prids_for_caid(aureader, cfg.ncd_ptab.ports[cl->port_idx].ncd->ncd_ftab.filts[0].caid);
+				}
+				else
+#endif
 				mk_user_au_ftab(aureader, &usr_filter);
 			}
 
@@ -1184,7 +1191,7 @@ static int8_t newcamd_auth_client(IN_ADDR_T ip, uint8_t *deskey)
 					uint32_t rprid;
 					found = 0;
 
-					if(pufilt->caid == aureader->caid)
+					if(pufilt->caid == aureader->caid && aureader->typ != R_EMU)
 					{
 						for(k = 0; k < aureader->nprov; k++)
 						{
diff --git a/module-stat.c b/module-stat.c
index ed0e85e4..ee179b79 100644
--- a/module-stat.c
+++ b/module-stat.c
@@ -897,7 +897,7 @@ void check_lb_auto_betatunnel_mode(ECM_REQUEST *er)
 
 uint16_t get_rdr_caid(struct s_reader *rdr)
 {
-	if(is_network_reader(rdr))
+	if(is_network_reader(rdr) || rdr->typ == R_EMU)
 	{
 		return 0; // reader caid is not real caid
 	}
@@ -1288,7 +1288,7 @@ void stat_get_best_reader(ECM_REQUEST *er)
 		for(ea = er->matching_rdr; ea; ea = ea->next)
 		{
 			rdr = ea->reader;
-			if(is_network_reader(rdr)) // reader caid is not real caid
+			if(is_network_reader(rdr) || rdr->typ == R_EMU) // reader caid is not real caid
 			{
 				prv = ea;
 				continue; // proxy can convert or reject
diff --git a/module-streamrelay.c b/module-streamrelay.c
index 69f4cb60..68b23df7 100644
--- a/module-streamrelay.c
+++ b/module-streamrelay.c
@@ -15,6 +15,12 @@
 #include "oscam-string.h"
 #include "oscam-time.h"
 
+#ifdef WITH_EMU
+#include "cscrypt/des.h"
+#include "module-emulator-osemu.h"
+#include "module-emulator-powervu.h"
+#endif
+
 #define STREAM_UNDEFINED 0x00
 #define STREAM_VIDEO     0x01
 #define STREAM_AUDIO     0x02
@@ -39,12 +45,27 @@ static pthread_mutex_t stream_server_mutex;
 static int32_t glistenfd, mod_idx, gconncount = 0, gconnfd[STREAM_SERVER_MAX_CONNECTIONS], stream_resptime[STREAM_SERVER_MAX_CONNECTIONS];
 static char ecm_src[STREAM_SERVER_MAX_CONNECTIONS][9];
 static IN_ADDR_T client_ip[STREAM_SERVER_MAX_CONNECTIONS], stream_host_ip[STREAM_SERVER_MAX_CONNECTIONS];
+#ifdef WITH_EMU
+#define STATIC /* none */
+#else
+#define STATIC static
+#endif
 static in_port_t client_port[STREAM_SERVER_MAX_CONNECTIONS];
 struct s_client *streamrelay_client[STREAM_SERVER_MAX_CONNECTIONS];
 
-static pthread_mutex_t fixed_key_srvid_mutex;
-static uint16_t stream_cur_srvid[STREAM_SERVER_MAX_CONNECTIONS];
-static stream_client_key_data key_data[STREAM_SERVER_MAX_CONNECTIONS];
+STATIC pthread_mutex_t fixed_key_srvid_mutex;
+STATIC uint16_t stream_cur_srvid[STREAM_SERVER_MAX_CONNECTIONS];
+STATIC stream_client_key_data key_data[STREAM_SERVER_MAX_CONNECTIONS];
+
+#ifdef WITH_EMU
+int8_t stream_server_thread_init = 0;
+int8_t emu_stream_emm_enabled = 0;
+uint8_t emu_stream_server_mutex_init = 0;
+int8_t stream_server_has_ecm[EMU_STREAM_SERVER_MAX_CONNECTIONS];
+pthread_mutex_t emu_fixed_key_data_mutex[EMU_STREAM_SERVER_MAX_CONNECTIONS];
+emu_stream_client_key_data emu_fixed_key_data[EMU_STREAM_SERVER_MAX_CONNECTIONS];
+LLIST *ll_emu_stream_delayed_keys[EMU_STREAM_SERVER_MAX_CONNECTIONS];
+#endif
 
 #ifdef MODULE_RADEGAST
 static int32_t gRadegastFd = 0;
@@ -139,10 +160,13 @@ static void radegast_client_ecm(stream_client_data *cdata)
 		}
 	}
 }
+#endif // MODULE_RADEGAST
 
+#ifdef WITH_EMU
 void ParseEcmData(stream_client_data *cdata)
 {
 	uint8_t *data = cdata->ecm_data;
+	uint8_t dcw[16];
 	uint16_t section_length = SCT_LEN(data);
 
 	if (section_length < 11)
@@ -150,10 +174,36 @@ void ParseEcmData(stream_client_data *cdata)
 		return;
 	}
 
-	cs_strncpy(ecm_src[cdata->connid], "radegast", sizeof(ecm_src[cdata->connid]));
-	radegast_client_ecm(cdata);
+	if (caid_is_powervu(cdata->caid))
+	{
+		if (data[11] > cdata->ecm_nb || (cdata->ecm_nb == 255 && data[11] == 0) || ((cdata->ecm_nb - data[11]) > 5))
+		{
+			cdata->ecm_nb = data[11];
+			cdata->key.connid = cdata->connid;
+			powervu_ecm(data, dcw, NULL, cdata->srvid, cdata->caid, cdata->tsid, cdata->onid, cdata->ens, &cdata->key);
+		}
+	}
+#ifdef MODULE_RADEGAST
+	else
+	{
+		cs_strncpy(ecm_src[cdata->connid], "radegast", sizeof(ecm_src[cdata->connid]));
+		radegast_client_ecm(cdata);
+	}
+#endif
 }
-#endif // MODULE_RADEGAST
+#else
+#define ParseEcmData radegast_client_ecm
+#endif
+
+#if DVBCSA_KEY_ECM
+#define DVBCSA_HEADER_ECM 1
+#define dvbcsa_bs_key_set(a,b) dvbcsa_bs_key_set_ecm(ecm,a,b)
+#else
+#define DVBCSA_HEADER_ECM 0
+#endif
+#ifndef STATIC_LIBDVBCSA
+#define STATIC_LIBDVBCSA 0
+#endif
 
 static void write_cw(ECM_REQUEST *er, int32_t connid)
 {
@@ -164,7 +214,11 @@ static void write_cw(ECM_REQUEST *er, int32_t connid)
 	{
 		if (has_dvbcsa_ecm)
 		{
+#ifdef WITH_EMU
+			dvbcsa_bs_key_set(er->cw, key_data[connid].key[0][EVEN]);
+#else
 			dvbcsa_bs_key_set(er->cw, key_data[connid].key[EVEN]);
+#endif
 		}
 	}
 
@@ -172,9 +226,18 @@ static void write_cw(ECM_REQUEST *er, int32_t connid)
 	{
 		if (has_dvbcsa_ecm)
 		{
+#ifdef WITH_EMU
+			dvbcsa_bs_key_set(er->cw + 8, key_data[connid].key[0][ODD]);
+#else
 			dvbcsa_bs_key_set(er->cw + 8, key_data[connid].key[ODD]);
+#endif
 		}
 	}
+#ifdef WITH_EMU
+	SAFE_MUTEX_LOCK(&emu_fixed_key_data_mutex[connid]);
+	emu_fixed_key_data[connid].csa_used = 1;
+	SAFE_MUTEX_UNLOCK(&emu_fixed_key_data_mutex[connid]);
+#endif
 }
 
 static void update_client_info(ECM_REQUEST *er, int32_t connid)
@@ -205,24 +268,20 @@ static void update_client_info(ECM_REQUEST *er, int32_t connid)
 bool stream_write_cw(ECM_REQUEST *er)
 {
 	int32_t i;
-	if (er->rc == E_FOUND)
+	bool cw_written = false;
+	//SAFE_MUTEX_LOCK(&fixed_key_srvid_mutex);
+	for (i = 0; i < STREAM_SERVER_MAX_CONNECTIONS; i++)
 	{
-		bool cw_written = false;
-		//SAFE_MUTEX_LOCK(&fixed_key_srvid_mutex);
-		for (i = 0; i < STREAM_SERVER_MAX_CONNECTIONS; i++)
+		if (stream_cur_srvid[i] == er->srvid)
 		{
-			if (stream_cur_srvid[i] == er->srvid)
-			{
-				write_cw(er, i);
-				update_client_info(er, i);
-				cw_written = true;
-				// don't return as there might be more connections for the same channel (e.g. recordings)
-			}
+			write_cw(er, i);
+			update_client_info(er, i);
+			cw_written = true;
+			// don't return as there might be more connections for the same channel (e.g. recordings)
 		}
-		//SAFE_MUTEX_UNLOCK(&fixed_key_srvid_mutex);
-		return cw_written;
 	}
-	return true;
+	//SAFE_MUTEX_UNLOCK(&fixed_key_srvid_mutex);
+	return cw_written;
 }
 
 static void SearchTsPackets(const uint8_t *buf, const uint32_t bufLength, uint16_t *packetSize, uint16_t *startOffset)
@@ -370,6 +429,9 @@ static void ParsePatData(stream_client_data *cdata)
 {
 	int32_t i;
 	uint16_t srvid;
+#ifdef __BISS__
+	cdata->STREAMpidcount = 0;
+#endif
 	for (i = 8; i + 7 < SCT_LEN(cdata->pat_data); i += 4)
 	{
 		srvid = b2i(2, cdata->pat_data + i);
@@ -388,6 +450,21 @@ static void ParsePatData(stream_client_data *cdata)
 	}
 }
 
+#ifdef WITH_EMU
+static int8_t stream_client_get_caid(stream_client_data *cdata)
+{
+	uint32_t tmp1 = (cdata->srvid << 16) | cdata->pmt_pid;
+	uint8_t tmp2[2];
+
+	if (emu_find_key('A', tmp1, 0, "FAKE", tmp2, 2, 0, 0, 0, NULL))
+	{
+		cdata->caid = b2i(2, tmp2);
+		return 1;
+	}
+	return 0;
+}
+#endif
+
 static void ParseDescriptors(const uint8_t *buffer, const uint16_t info_length, uint8_t *type)
 {
 	uint32_t i;
@@ -563,6 +640,9 @@ static void ParsePmtData(stream_client_data *cdata)
 			case 0xD1:
 			case 0xEA:
 			{
+#ifdef WITH_EMU
+				cdata->video_pid = elementary_pid;
+#endif
 				cs_log_dbg(D_READER, "Stream client %i found video pid: 0x%04X (%i)",
 							cdata->connid, elementary_pid, elementary_pid);
 				stream_parse_pmt_ca_descriptor(cdata->pmt_data, i, 5, es_info_length, cdata);
@@ -577,6 +657,15 @@ static void ParsePmtData(stream_client_data *cdata)
 			case 0x2E:
 			case 0x81:
 			{
+#ifdef WITH_EMU
+				if (cdata->audio_pid_count >= EMU_STREAM_MAX_AUDIO_SUB_TRACKS)
+				{
+					continue;
+				}
+
+				cdata->audio_pids[cdata->audio_pid_count] = elementary_pid;
+				cdata->audio_pid_count++;
+#endif
 				cs_log_dbg(D_READER, "Stream client %i found audio pid: 0x%04X (%i)",
 							cdata->connid, elementary_pid, elementary_pid);
 				break;
@@ -589,19 +678,87 @@ static void ParsePmtData(stream_client_data *cdata)
 				ParseDescriptors(cdata->pmt_data + i + 5, es_info_length, &type);
 				if (type == STREAM_AUDIO)
 				{
+#ifdef WITH_EMU
+					if (cdata->audio_pid_count >= EMU_STREAM_MAX_AUDIO_SUB_TRACKS)
+					{
+						continue;
+					}
+
+					cdata->audio_pids[cdata->audio_pid_count] = elementary_pid;
+					cdata->audio_pid_count++;
+#endif
 					cs_log_dbg(D_READER, "Stream client %i found audio pid: 0x%04X (%i)",
 								cdata->connid, elementary_pid, elementary_pid);
 				}
 				else if (type == STREAM_TELETEXT)
 				{
+#ifdef WITH_EMU
+					cdata->teletext_pid = elementary_pid;
+#endif
 					cs_log_dbg(D_READER, "Stream client %i found teletext pid: 0x%04X (%i)",
 								cdata->connid, elementary_pid, elementary_pid);
 				}
 				break;
 			}
 		}
+#ifdef __BISS__
+		cdata->STREAMpids[cdata->STREAMpidcount] = elementary_pid;
+		cdata->STREAMpidcount++;
+#endif
+	}
+#ifdef WITH_EMU
+	// If we haven't found a CAID for this service,
+	// search the keyDB for a fake one
+	if (cdata->caid == NO_CAID_VALUE && stream_client_get_caid(cdata) == 1)
+	{
+		cs_log_dbg(D_READER, "Stream client %i found fake caid: 0x%04X (%i)",
+					cdata->connid, cdata->caid, cdata->caid);
+	}
+#endif
+}
+
+#ifdef WITH_EMU
+static void ParseCatData(stream_client_data *cdata)
+{
+	uint8_t *data = cdata->cat_data;
+	uint32_t i;
+
+	for (i = 8; i < (b2i(2, data + 1) & 0xFFF) - 1; i += data[i + 1] + 2)
+	{
+		if (data[i] != 0x09)
+		{
+			continue;
+		}
+
+		uint16_t caid = b2i(2, data + i + 2);
+
+		if (caid_is_powervu(caid)) // add all supported caids here
+		{
+			if (cdata->caid == NO_CAID_VALUE)
+			{
+				cdata->caid = caid;
+			}
+			cdata->emm_pid = b2i(2, data + i + 4) & 0x1FFF;;
+			cs_log_dbg(D_READER, "Stream client %i found emm pid: 0x%04X (%i)",
+						cdata->connid, cdata->emm_pid, cdata->emm_pid);
+			break;
+		}
+	}
+}
+
+static void ParseEmmData(stream_client_data *cdata)
+{
+	uint32_t keysAdded = 0;
+
+	emu_process_emm(NULL, cdata->caid, cdata->emm_data, &keysAdded);
+
+	if (keysAdded)
+	{
+		//refresh_entitlements(rdr);
+		cs_log("Stream client %i found %i keys", cdata->connid, keysAdded);
 	}
 }
+#endif
 
 static void ParseTsPackets(stream_client_data *data, uint8_t *stream_buf, uint32_t bufLength, uint16_t packetSize)
 {
@@ -651,7 +808,11 @@ static void ParseTsPackets(stream_client_data *data, uint8_t *stream_buf, uint32
 	}
 }
 
-static void decrypt(struct dvbcsa_bs_batch_s *tsbbatch, uint16_t fill[2], const uint8_t oddeven, const int32_t connid)
+#ifdef WITH_EMU
+static void decrypt_csa(struct dvbcsa_bs_batch_s *tsbbatch, uint16_t fill[2], const uint8_t oddeven, const int32_t connid, uint8_t cw_type)
+#else
+static void decrypt_csa(struct dvbcsa_bs_batch_s *tsbbatch, uint16_t fill[2], const uint8_t oddeven, const int32_t connid)
+#endif
 {
 	if (fill[oddeven] > 0)
 	{
@@ -669,10 +830,531 @@ static void decrypt(struct dvbcsa_bs_batch_s *tsbbatch, uint16_t fill[2], const
 
 		fill[oddeven] = 0;
 
+#ifdef WITH_EMU
+		dvbcsa_bs_decrypt(key_data[connid].key[cw_type][oddeven], tsbbatch, 184);
+#else
 		dvbcsa_bs_decrypt(key_data[connid].key[oddeven], tsbbatch, 184);
+#endif
 	}
 }
-#define decrypt(a) decrypt(tsbbatch, fill, a, data->connid)
+#ifndef WITH_EMU
+#define decrypt(a) decrypt_csa(tsbbatch, fill, a, data->connid)
+#else // multiple cw
+#define decrypt(a) decrypt_csa(tsbbatch, fill, a, data->connid, 0)
+#define decrypt_pvu(a, b) decrypt_csa(tsbbatch, fill, a, data->connid, b)
+#endif
+
+#ifdef WITH_EMU
+static void DescrambleTsPacketsPowervu(stream_client_data *data, uint8_t *stream_buf, uint32_t bufLength, uint16_t packetSize, struct dvbcsa_bs_batch_s *tsbbatch)
+{
+	uint32_t i, j, tsHeader, *deskey;
+	uint16_t pid, offset, fill[2] = {0,0};
+	uint8_t *pdata, payloadStart, oddeven = 0, cw_type = 0;
+	int8_t oddKeyUsed;
+
+	for (i = 0; i < bufLength; i += packetSize)
+	{
+		tsHeader = b2i(4, stream_buf + i);
+		pid = (tsHeader & 0x1FFF00) >> 8;
+		payloadStart = (tsHeader & 0x400000) >> 22;
+		offset = (tsHeader & 0x20) ? 4 + stream_buf[i + 4] + 1 : 4;
+
+		if (packetSize - offset < 1)
+		{
+			continue;
+		}
+
+		if (emu_stream_emm_enabled && pid == 0x0001 && data->have_cat_data != 1) // Search the CAT for EMM pids
+		{
+			// set to null pid
+			stream_buf[i + 1] |= 0x1F;
+			stream_buf[i + 2] = 0xFF;
+
+			ParseTsData(0x01, 0xFF, 8, &data->have_cat_data, data->cat_data, sizeof(data->cat_data),
+						&data->cat_data_pos, payloadStart, stream_buf + i + offset, packetSize - offset, ParseCatData, data);
+			continue;
+		}
+
+		if (emu_stream_emm_enabled && data->emm_pid && pid == data->emm_pid) // Process the EMM data
+		{
+			// set to null pid
+			stream_buf[i + 1] |= 0x1F;
+			stream_buf[i + 2] = 0xFF;
+
+			ParseTsData(0x80, 0xF0, 3, &data->have_emm_data, data->emm_data, sizeof(data->emm_data),
+						&data->emm_data_pos, payloadStart, stream_buf + i + offset, packetSize - offset, ParseEmmData, data);
+			continue;
+		}
+
+		if (data->ecm_pid && pid == data->ecm_pid) // Process the ECM data
+		{
+			stream_server_has_ecm[data->connid] = 1;
+			// set to null pid
+			stream_buf[i + 1] |= 0x1F;
+			stream_buf[i + 2] = 0xFF;
+			ParseTsData(0x80, 0xFE, 3, &data->have_ecm_data, data->ecm_data, sizeof(data->ecm_data),
+						&data->ecm_data_pos, payloadStart, stream_buf + i + offset, packetSize - offset, ParseEcmData, data);
+			continue;
+		}
+
+		if ((tsHeader & 0xC0) == 0)
+		{
+			continue;
+		}
+
+		if (data->key.csa_used)
+		{
+			stream_buf[i + 3] &= 0x3f; // consider it decrypted now
+			oddeven = (tsHeader & 0xC0) == 0xC0 ? ODD: EVEN;
+			decrypt_pvu(oddeven == ODD ? EVEN : ODD, cw_type);
+
+			if (pid == data->video_pid) // start with video pid, since it is most dominant
+			{
+				cw_type = PVU_CW_VID;
+				tsbbatch[fill[oddeven]].data = &stream_buf[i + offset];
+				tsbbatch[fill[oddeven]].len = packetSize - offset;
+				fill[oddeven]++;
+				if (fill[oddeven] > cluster_size - 1)
+				{
+					decrypt_pvu(oddeven, cw_type);
+				}
+			}
+			else
+			{
+				if (cw_type != PVU_CW_VID)
+				{
+					decrypt_pvu(oddeven, PVU_CW_VID);
+				}
+				for (j = 0; j < data->audio_pid_count; j++)
+				{
+					if (pid == data->audio_pids[j])
+					{
+						if (cw_type != PVU_CW_A1 + j)
+						{
+							decrypt_pvu(oddeven, PVU_CW_A1 + j);
+						}
+						cw_type = PVU_CW_A1 + j;
+						tsbbatch[fill[oddeven]].data = &stream_buf[i + offset];
+						tsbbatch[fill[oddeven]].len = packetSize - offset;
+						fill[oddeven]++;
+						if (fill[oddeven] > cluster_size - 1)
+						{
+							decrypt_pvu(oddeven, cw_type);
+						}
+					}
+				}
+			}
+		}
+		else
+		{
+			oddKeyUsed = (tsHeader & 0xC0) == 0xC0 ? 1 : 0;
+			deskey = NULL;
+
+			if (pid == data->video_pid)
+			{
+				deskey = data->key.pvu_des_ks[PVU_CW_VID][oddKeyUsed];
+			}
+			else
+			{
+				for (j = 0; j < data->audio_pid_count; j++)
+				{
+					if (pid == data->audio_pids[j])
+					{
+						deskey = data->key.pvu_des_ks[PVU_CW_A1 + j][oddKeyUsed];
+					}
+				}
+			}
+
+			if (deskey == NULL)
+			{
+				deskey = data->key.pvu_des_ks[PVU_CW_HSD][oddKeyUsed];
+			}
+
+			for (j = offset; j + 7 < 188; j += 8)
+			{
+				pdata = stream_buf + i + j;
+				des(pdata, deskey, 0);
+			}
+
+			stream_buf[i + 3] &= 0x3F;
+		}
+	}
+	if (data->key.csa_used) { decrypt_pvu(oddeven, cw_type); }
+}
+static void DescrambleTsPacketsRosscrypt1(emu_stream_client_data *data, uint8_t *stream_buf, uint32_t bufLength, uint16_t packetSize)
+{
+	int8_t is_av_pid;
+	int32_t j;
+
+	uint8_t scramblingControl;
+	uint16_t pid, offset;
+	uint32_t i, tsHeader;
+
+	for (i = 0; i < bufLength; i += packetSize)
+	{
+		tsHeader = b2i(4, stream_buf + i);
+		pid = (tsHeader & 0x1FFF00) >> 8;
+		scramblingControl = tsHeader & 0xC0;
+
+		if (tsHeader & 0x20)
+		{
+			offset = 4 + stream_buf[i + 4] + 1;
+		}
+		else
+		{
+			offset = 4;
+		}
+
+		if (packetSize - offset < 1)
+		{
+			continue;
+		}
+
+		if (scramblingControl == 0)
+		{
+			continue;
+		}
+
+		if (!(stream_buf[i + 3] & 0x10))
+		{
+			stream_buf[i + 3] &= 0x3F;
+			continue;
+		}
+
+		is_av_pid = 0;
+
+		if (pid == data->video_pid)
+		{
+			is_av_pid = 1;
+		}
+		else
+		{
+			for (j = 0; j < data->audio_pid_count; j++)
+			{
+				if (pid == data->audio_pids[j])
+				{
+					is_av_pid = 1;
+					break;
+				}
+			}
+		}
+
+		if (is_av_pid)
+		{
+			static uint8_t dyn_key[184];
+			static uint8_t last_packet[184];
+
+			// Reset key on channel change
+			if (data->reset_key_data == 1)
+			{
+				memset(dyn_key, 0x00, 184);
+				memset(last_packet, 0x00, 184);
+				data->reset_key_data = 0;
+			}
+
+			if (memcmp(last_packet, stream_buf + i + 4, 184) == 0)
+			{
+				if (memcmp(dyn_key, stream_buf + i + 4, 184) != 0)
+				{
+					memcpy(dyn_key, stream_buf + i + 4, 184);
+				}
+			}
+			else
+			{
+				memcpy(last_packet, stream_buf + i + 4, 184);
+			}
+
+			for (j = 0; j < 184; j++)
+			{
+				stream_buf[i + 4 + j] ^= dyn_key[j];
+			}
+
+			stream_buf[i + 3] &= 0x3F;
+		}
+	}
+}
+
+static void DescrambleTsPacketsCompel(emu_stream_client_data *data, uint8_t *stream_buf, uint32_t bufLength, uint16_t packetSize)
+{
+	int8_t is_pes_pid; // any PES pid
+	int32_t j;
+	const int8_t limit = 4;
+
+	uint8_t scramblingControl;
+	uint16_t pid, offset;
+	uint32_t i, tsHeader;
+
+	for (i = 0; i < bufLength; i += packetSize)
+	{
+		tsHeader = b2i(4, stream_buf + i);
+		pid = (tsHeader & 0x1FFF00) >> 8;
+		scramblingControl = tsHeader & 0xC0;
+
+		if (tsHeader & 0x20)
+		{
+			offset = 4 + stream_buf[i + 4] + 1;
+		}
+		else
+		{
+			offset = 4;
+		}
+
+		if (packetSize - offset < 1)
+		{
+			continue;
+		}
+
+		if (scramblingControl == 0)
+		{
+			continue;
+		}
+
+		if (!(stream_buf[i + 3] & 0x10))
+		{
+			stream_buf[i + 3] &= 0x3F;
+			continue;
+		}
+
+		is_pes_pid = 0;
+
+		if (pid == data->video_pid)
+		{
+			is_pes_pid = 1;
+		}
+		else if (pid == data->teletext_pid)
+		{
+			is_pes_pid = 1;
+		}
+		else
+		{
+			for (j = 0; j < data->audio_pid_count; j++)
+			{
+				if (pid == data->audio_pids[j])
+				{
+					is_pes_pid = 1;
+					break;
+				}
+			}
+		}
+
+		if (is_pes_pid)
+		{
+			static uint8_t dyn_key[184];
+			static uint8_t found_key_bytes[184];
+			static uint8_t found_key_bytes_count = 8;
+			static uint8_t lastScramblingControl = 0xFF;
+
+			int8_t matches00 = 0;
+			int8_t matchesFF = 0;
+			int8_t last00_was_good = 0;
+			int8_t lastFF_was_good = 0;
+
+			// Reset key when scrambling control changes from odd to even
+			// and vice versa (every ~53 seconds) or when we change channel
+			if (lastScramblingControl != scramblingControl)
+			{
+				memset(dyn_key, 0x00, 184);
+				memset(found_key_bytes, 0, 184);
+				found_key_bytes_count = 8;
+				lastScramblingControl = scramblingControl;
+
+				//cs_log_dbg(D_READER, "resetting key data (scrambling control: %02X)", scramblingControl);
+			}
+
+			for (j = 8; j < 184; j++)
+			{
+				if (found_key_bytes_count == 184)
+				{
+					break;
+				}
+
+				if (stream_buf[i + 4 + j] == 0x00)
+				{
+					last00_was_good = 1;
+					matches00++;
+
+					if (matches00 > limit && found_key_bytes[j] == 0)
+					{
+						dyn_key[j] = 0x00;
+						found_key_bytes[j] = 1;
+						found_key_bytes_count++;
+					}
+				}
+				else if (stream_buf[i + 4 + j] == 0x3F)
+				{
+					last00_was_good = 1;
+					matches00++;
+
+					if (matches00 > limit && found_key_bytes[j] == 0)
+					{
+						dyn_key[j] = 0x3F;
+						found_key_bytes[j] = 1;
+						found_key_bytes_count++;
+					}
+				}
+				else
+				{
+					if (last00_was_good == 1)
+					{
+						last00_was_good = 0;
+						matches00--;
+					}
+					else
+					{
+						matches00 -= 2;
+					}
+
+					if (matches00 < 0)
+					{
+						matches00 = 0;
+					}
+				}
+
+				if (stream_buf[i + 4 + j] == 0xC0)
+				{
+					lastFF_was_good = 1;
+					matchesFF++;
+
+					if (matchesFF > limit && found_key_bytes[j] == 0)
+					{
+						dyn_key[j] = 0x3F;
+						found_key_bytes[j] = 1;
+						found_key_bytes_count++;
+					}
+				}
+				else if (stream_buf[i + 4 + j] == 0xFF)
+				{
+					lastFF_was_good = 1;
+					matchesFF++;
+
+					if (matchesFF > limit && found_key_bytes[j] == 0)
+					{
+						dyn_key[j] = 0x00;
+						found_key_bytes[j] = 1;
+						found_key_bytes_count++;
+					}
+				}
+				else
+				{
+					if (lastFF_was_good == 1)
+					{
+						lastFF_was_good = 0;
+						matchesFF--;
+					}
+					else
+					{
+						matchesFF -= 2;
+					}
+
+					if (matchesFF < 0)
+					{
+						matchesFF = 0;
+					}
+				}
+			}
+
+			for (j = 183; j >= 8; j--)
+			{
+				if (found_key_bytes_count == 184)
+				{
+					break;
+				}
+
+				if (stream_buf[i + 4 + j] == 0x00)
+				{
+					last00_was_good = 1;
+					matches00++;
+
+					if (matches00 > limit && found_key_bytes[j] == 0)
+					{
+						dyn_key[j] = 0x00;
+						found_key_bytes[j] = 1;
+						found_key_bytes_count++;
+					}
+				}
+				else if (stream_buf[i + 4 + j] == 0x3F)
+				{
+					last00_was_good = 1;
+					matches00++;
+
+					if (matches00 > limit && found_key_bytes[j] == 0)
+					{
+						dyn_key[j] = 0x3F;
+						found_key_bytes[j] = 1;
+						found_key_bytes_count++;
+					}
+				}
+				else
+				{
+					if (last00_was_good == 1)
+					{
+						last00_was_good = 0;
+						matches00--;
+					}
+					else
+					{
+						matches00 -= 2;
+					}
+
+					if (matches00 < 0)
+					{
+						matches00 = 0;
+					}
+				}
+
+				if (stream_buf[i + 4 + j] == 0xC0)
+				{
+					lastFF_was_good = 1;
+					matchesFF++;
+
+					if (matchesFF > limit && found_key_bytes[j] == 0)
+					{
+						dyn_key[j] = 0x3F;
+						found_key_bytes[j] = 1;
+						found_key_bytes_count++;
+					}
+				}
+				else if (stream_buf[i + 4 + j] == 0xFF)
+				{
+					lastFF_was_good = 1;
+					matchesFF++;
+
+					if (matchesFF > limit && found_key_bytes[j] == 0)
+					{
+						dyn_key[j] = 0x00;
+						found_key_bytes[j] = 1;
+						found_key_bytes_count++;
+					}
+				}
+				else
+				{
+					if (lastFF_was_good == 1)
+					{
+						lastFF_was_good = 0;
+						matchesFF--;
+					}
+					else
+					{
+						matchesFF -= 2;
+					}
+
+					if (matchesFF < 0)
+					{
+						matchesFF = 0;
+					}
+				}
+			}
+
+			for (j = 8; j < 184; j++)
+			{
+				stream_buf[i + 4 + j] ^= dyn_key[j];
+			}
+		}
+
+		stream_buf[i + 3] &= 0x3F; // Clear scrambling bits
+	}
+}
+#endif // WITH_EMU
 
 static void DescrambleTsPackets(stream_client_data *data, uint8_t *stream_buf, uint32_t bufLength, uint16_t packetSize, struct dvbcsa_bs_batch_s *tsbbatch)
 {
@@ -697,6 +1379,31 @@ static void DescrambleTsPackets(stream_client_data *data, uint8_t *stream_buf, u
 			continue;
 		}
 #ifdef MODULE_RADEGAST
+#ifdef __BISS__
+		if(data->ecm_pid == 0x1FFF && caid_is_biss_fixed(data->caid))
+		{
+			uint32_t j, n;
+			uint16_t ecm_len = 7;
+			data->ecm_data[0] = 0x80; // to pass the cache check it must be 0x80 or 0x81
+			data->ecm_data[1] = 0x00;
+			data->ecm_data[2] = 0x04;
+			i2b_buf(2, data->srvid, data->ecm_data + 3);
+			i2b_buf(2, data->pmt_pid, data->ecm_data + 5);
+			for(j = 0, n = 7; j < data->STREAMpidcount; j++, n += 2)
+			{
+				i2b_buf(2, data->STREAMpids[j], data->ecm_data + n);
+				data->ecm_data[2] += 2;
+				ecm_len += 2;
+			}
+			data->ens &= 0x0FFFFFFF; // clear top 4 bits (in case of DVB-T/C or garbage), prepare for flagging
+			data->ens |= 0xA0000000; // flag to emu: this is the namespace, not a pid
+			i2b_buf(2, data->tsid, data->ecm_data + ecm_len);     // place tsid after the last stream pid
+			i2b_buf(2, data->onid, data->ecm_data + ecm_len + 2); // place onid right after tsid
+			i2b_buf(4, data->ens, data->ecm_data + ecm_len + 4);  // place namespace at the end of the ecm
+			data->ecm_data[2] += 8;
+			ParseEcmData(data);
+		} else
+#endif // __BISS__
 		if (data->ecm_pid && pid == data->ecm_pid) // Process the ECM data
 		{
 			// set to null pid
@@ -788,6 +1495,9 @@ static void stream_client_disconnect(stream_client_conn_data *conndata)
 
 	SAFE_MUTEX_LOCK(&fixed_key_srvid_mutex);
 	stream_cur_srvid[conndata->connid] = NO_SRVID_VALUE;
+#ifdef WITH_EMU
+	stream_server_has_ecm[conndata->connid] = 0;
+#endif
 	SAFE_MUTEX_UNLOCK(&fixed_key_srvid_mutex);
 
 	SAFE_MUTEX_LOCK(&stream_server_mutex);
@@ -882,10 +1592,12 @@ static void *stream_client_handler(void *arg)
 	uint8_t descrambling = 0;
 
 	struct timeb start, end;
-
+#ifdef WITH_EMU
+	int32_t cur_dvb_buffer_size, cur_dvb_buffer_wait;
+#else
 	const int32_t cur_dvb_buffer_size = DVB_BUFFER_SIZE_CSA;
 	const int32_t cur_dvb_buffer_wait = DVB_BUFFER_WAIT_CSA;
-
+#endif
 	struct dvbcsa_bs_batch_s *tsbbatch;
 
 	create_streamrelay_client(conndata);
@@ -996,8 +1708,16 @@ static void *stream_client_handler(void *arg)
 		return NULL;
 	}
 
+#ifndef WITH_EMU
 	key_data[conndata->connid].key[ODD]  = dvbcsa_bs_key_alloc();
 	key_data[conndata->connid].key[EVEN] = dvbcsa_bs_key_alloc();
+#else
+	for (i = 0; i < EMU_STREAM_MAX_AUDIO_SUB_TRACKS + 2; i++)
+	{
+		key_data[conndata->connid].key[i][ODD]  = dvbcsa_bs_key_alloc();
+		key_data[conndata->connid].key[i][EVEN] = dvbcsa_bs_key_alloc();
+	}
+#endif
 
 	if (!cs_malloc(&tsbbatch, (cluster_size + 1) * sizeof(struct dvbcsa_bs_batch_s)))
 	{
@@ -1010,6 +1730,9 @@ static void *stream_client_handler(void *arg)
 
 	SAFE_MUTEX_LOCK(&fixed_key_srvid_mutex);
 	stream_cur_srvid[conndata->connid] = data->srvid;
+#ifdef WITH_EMU
+	stream_server_has_ecm[conndata->connid] = 0;
+#endif
 	SAFE_MUTEX_UNLOCK(&fixed_key_srvid_mutex);
 
 	cs_log("Stream client %i request. host=%s port=%d path=%s (%s)", conndata->connid, conndata->stream_host, cfg.stream_source_port, stream_path, !cfg.stream_client_source_host ? "config" : strchr(http_host,':') ? "http header" : "client ip");
@@ -1032,6 +1755,9 @@ static void *stream_client_handler(void *arg)
 	data->have_cat_data = 0;
 	data->have_ecm_data = 0;
 	data->have_emm_data = 0;
+#ifdef WITH_EMU
+	data->reset_key_data = 1;
+#endif
 
 	while (!exit_oscam && clientStatus != -1 && streamConnectErrorCount < 3
 			&& streamDataErrorCount < 15)
@@ -1053,6 +1779,18 @@ static void *stream_client_handler(void *arg)
 				&& (streamConnectErrorCount < 3 || streamDataErrorCount < 15))
 #endif
 		{
+#ifdef WITH_EMU
+			if (data->key.csa_used)
+			{
+				cur_dvb_buffer_size = EMU_DVB_BUFFER_SIZE_CSA;
+				cur_dvb_buffer_wait = EMU_DVB_BUFFER_WAIT_CSA;
+			}
+			else
+			{
+				cur_dvb_buffer_size = EMU_DVB_BUFFER_SIZE_DES;
+				cur_dvb_buffer_wait = EMU_DVB_BUFFER_WAIT_DES;
+			}
+#endif
 			if(!streamrelay_client[conndata->connid] || streamrelay_client[conndata->connid]->kill)
 			{
 				clientStatus = -1;
@@ -1155,13 +1893,30 @@ static void *stream_client_handler(void *arg)
 					// We have both PAT and PMT data - We can start descrambling
 					if (data->have_pat_data == 1 && data->have_pmt_data == 1)
 					{
-						if (chk_ctab_ex(data->caid, &cfg.stream_relay_ctab) && (data->caid != 0xA101 || data->caid == NO_CAID_VALUE))
+						if (chk_ctab_ex(data->caid, &cfg.stream_relay_ctab))
 						{
+#ifdef WITH_EMU
+							if (caid_is_powervu(data->caid))
+							{
+								DescrambleTsPacketsPowervu(data, stream_buf + startOffset, packetCount * packetSize, packetSize, tsbbatch);
+							}
+							else if (data->caid == 0xA101) // Rosscrypt1
+							{
+								DescrambleTsPacketsRosscrypt1(data, stream_buf + startOffset, packetCount * packetSize, packetSize);
+							}
+							else if (data->caid == NO_CAID_VALUE) // Compel
+							{
+								DescrambleTsPacketsCompel(data, stream_buf + startOffset, packetCount * packetSize, packetSize);
+							}
+							else
+#endif // WITH_EMU
+							{
 								DescrambleTsPackets(data, stream_buf + startOffset, packetCount * packetSize, packetSize, tsbbatch);
 								if (!descrambling && cfg.stream_relay_buffer_time) {
 									cs_sleepms(cfg.stream_relay_buffer_time);
 									descrambling = 1;
 								}
+							}
 						}
 						else
 						{
@@ -1201,9 +1956,16 @@ static void *stream_client_handler(void *arg)
 
 	NULLFREE(http_buf);
 	NULLFREE(stream_buf);
-
+#ifndef WITH_EMU
 	dvbcsa_bs_key_free(key_data[conndata->connid].key[ODD]);
 	dvbcsa_bs_key_free(key_data[conndata->connid].key[EVEN]);
+#else
+	for (i = 0; i < EMU_STREAM_MAX_AUDIO_SUB_TRACKS + 2; i++)
+	{
+		dvbcsa_bs_key_free(key_data[conndata->connid].key[i][ODD]);
+		dvbcsa_bs_key_free(key_data[conndata->connid].key[i][EVEN]);
+	}
+#endif
 	NULLFREE(tsbbatch);
 
 	NULLFREE(data);
@@ -1231,6 +1993,7 @@ static void *stream_server(void)
 
 	cluster_size = dvbcsa_bs_batch_size();
 	has_dvbcsa_ecm = (DVBCSA_HEADER_ECM);
+
 #if !DVBCSA_KEY_ECM
 #pragma message "WARNING: Streamrelay is compiled without dvbcsa ecm headers! ECM processing via Streamrelay will not work!"
 #endif
@@ -1258,6 +2021,9 @@ static void *stream_server(void)
 	for (i = 0; i < STREAM_SERVER_MAX_CONNECTIONS; i++)
 	{
 		stream_cur_srvid[i] = NO_SRVID_VALUE;
+#ifdef WITH_EMU
+		stream_server_has_ecm[i] = 0;
+#endif
 	}
 	SAFE_MUTEX_UNLOCK(&fixed_key_srvid_mutex);
 
@@ -1383,12 +2149,88 @@ void *streamrelay_handler(struct s_client *UNUSED(cl), uint8_t *UNUSED(mbuf), in
 			snprintf(authtmp, sizeof(authtmp), "%s:%s", cfg.stream_source_auth_user, cfg.stream_source_auth_password);
 			b64encode(authtmp, cs_strlen(authtmp), &stream_source_auth);
 		}
+
+#ifdef WITH_EMU
+		emu_stream_emm_enabled = cfg.emu_stream_emm_enabled;
+#endif
+
 		mod_idx = module_idx;
 		start_thread("stream_server", stream_server, NULL, NULL, 1, 1);
 	}
 	return NULL;
 }
 
+#if WITH_EMU
+void *stream_key_delayer(void *UNUSED(arg))
+{
+	int32_t i, j;
+	const uint8_t ecm = 0;
+	emu_stream_client_key_data *cdata;
+	LL_ITER it;
+	emu_stream_cw_item *item;
+	struct timeb t_now;
+
+	while (!exit_oscam)
+	{
+		cs_ftime(&t_now);
+
+		for (i = 0; i < STREAM_SERVER_MAX_CONNECTIONS; i++)
+		{
+			it = ll_iter_create(ll_emu_stream_delayed_keys[i]);
+
+			while ((item = ll_iter_next(&it)))
+			{
+				if (comp_timeb(&t_now, &item->write_time) < 0)
+				{
+					break;
+				}
+
+				SAFE_MUTEX_LOCK(&emu_fixed_key_data_mutex[i]);
+
+				cdata = &emu_fixed_key_data[i];
+
+				for (j = 0; j < EMU_STREAM_MAX_AUDIO_SUB_TRACKS + 2; j++)
+				{
+					if (item->csa_used)
+					{
+						if (item->is_even)
+						{
+							dvbcsa_bs_key_set(item->cw[j], key_data[cdata->connid].key[j][EVEN]);
+						}
+						else
+						{
+							dvbcsa_bs_key_set(item->cw[j], key_data[cdata->connid].key[j][ODD]);
+						}
+
+						cdata->csa_used = 1;
+					}
+					else
+					{
+						if (item->is_even)
+						{
+							des_set_key(item->cw[j], cdata->pvu_des_ks[j][0]);
+						}
+						else
+						{
+							des_set_key(item->cw[j], cdata->pvu_des_ks[j][1]);
+						}
+
+						cdata->csa_used = 0;
+					}
+				}
+				SAFE_MUTEX_UNLOCK(&emu_fixed_key_data_mutex[i]);
+
+				ll_iter_remove_data(&it);
+			}
+		}
+
+		cs_sleepms(25);
+	}
+
+	return NULL;
+}
+#endif
+
 void stop_stream_server(void)
 {
 	int32_t i;
diff --git a/module-streamrelay.h b/module-streamrelay.h
index 4425e020..42ec2351 100644
--- a/module-streamrelay.h
+++ b/module-streamrelay.h
@@ -10,26 +10,47 @@
 #define DVB_BUFFER_WAIT_CSA 188*(DVB_MAX_TS_PACKETS-128)
 #define DVB_BUFFER_SIZE DVB_BUFFER_SIZE_CSA
 
-#include "cscrypt/md5.h"
-#include <dvbcsa/dvbcsa.h>
-#if DVBCSA_KEY_ECM
-#define DVBCSA_HEADER_ECM 1
-#define dvbcsa_bs_key_set(a,b) dvbcsa_bs_key_set_ecm(ecm,a,b)
-#else
-#define DVBCSA_HEADER_ECM 0
+#ifdef WITH_EMU
+#define EMU_STREAM_MAX_AUDIO_SUB_TRACKS 4
+#define EMU_DVB_BUFFER_SIZE_CSA DVB_BUFFER_SIZE_CSA
+#define EMU_DVB_BUFFER_WAIT_CSA DVB_BUFFER_WAIT_CSA
+#define EMU_DVB_BUFFER_SIZE_DES 188*32
+#define EMU_DVB_BUFFER_WAIT_DES 188*29
+#define EMU_STREAM_SERVER_MAX_CONNECTIONS STREAM_SERVER_MAX_CONNECTIONS
+#define emu_fixed_key_srvid_mutex fixed_key_srvid_mutex
+#define emu_stream_cur_srvid stream_cur_srvid
+#define emu_stream_client_data stream_client_data
 #endif
-#ifndef STATIC_LIBDVBCSA
-#define STATIC_LIBDVBCSA 0
+
+//#define __BISS__
+#ifdef __BISS__
+#define MAX_STREAM_PIDS 32
 #endif
 
+#include "cscrypt/md5.h"
+#include <dvbcsa/dvbcsa.h>
+
 #define EVEN 0
 #define ODD 1
 
 typedef struct
 {
+#ifdef WITH_EMU
+	struct dvbcsa_bs_key_s *key[EMU_STREAM_MAX_AUDIO_SUB_TRACKS + 2][2];
+#else
 	struct dvbcsa_bs_key_s *key[2];
+#endif
 } stream_client_key_data;
 
+#ifdef WITH_EMU
+typedef struct
+{
+	uint32_t pvu_des_ks[EMU_STREAM_MAX_AUDIO_SUB_TRACKS + 2][2][32];
+	int8_t csa_used;
+	int32_t connid;
+} emu_stream_client_key_data;
+#endif
+
 typedef struct
 {
 	int32_t connid;
@@ -57,7 +78,20 @@ typedef struct
 	uint16_t ecm_pid;
 	uint16_t emm_pid;
 	uint16_t pcr_pid;
+#ifdef __BISS__
+	uint8_t STREAMpidcount;
+	uint16_t STREAMpids[MAX_STREAM_PIDS];
+#endif
 	uint8_t ecm_md5[MD5_DIGEST_LENGTH];
+#ifdef WITH_EMU
+	int16_t ecm_nb;
+	int8_t reset_key_data;
+	uint16_t video_pid;
+	uint16_t teletext_pid;
+	uint16_t audio_pids[EMU_STREAM_MAX_AUDIO_SUB_TRACKS];
+	uint8_t audio_pid_count;
+	emu_stream_client_key_data key;
+#endif
 } stream_client_data;
 
 void init_stream_server(void);
@@ -65,6 +99,30 @@ void stop_stream_server(void);
 
 bool stream_write_cw(ECM_REQUEST *er);
 
+#ifdef WITH_EMU
+extern int8_t stream_server_thread_init;
+extern pthread_mutex_t fixed_key_srvid_mutex;
+extern uint16_t stream_cur_srvid[STREAM_SERVER_MAX_CONNECTIONS];
+extern int8_t stream_server_has_ecm[STREAM_SERVER_MAX_CONNECTIONS];
+extern uint8_t emu_stream_server_mutex_init;
+extern bool has_dvbcsa_ecm;
+
+typedef struct
+{
+	struct timeb write_time;
+	int8_t csa_used;
+	int8_t is_even;
+	uint8_t cw[8][8];
+} emu_stream_cw_item;
+
+extern pthread_mutex_t emu_fixed_key_data_mutex[EMU_STREAM_SERVER_MAX_CONNECTIONS];
+extern stream_client_key_data key_data[STREAM_SERVER_MAX_CONNECTIONS];
+extern emu_stream_client_key_data emu_fixed_key_data[EMU_STREAM_SERVER_MAX_CONNECTIONS];
+
+extern LLIST *ll_emu_stream_delayed_keys[EMU_STREAM_SERVER_MAX_CONNECTIONS];
+void *stream_key_delayer(void *arg);
+#endif // WITH_EMU
+
 #endif // MODULE_STREAMRELAY
 
 #endif // MODULE_STREAMRELAY_H_
diff --git a/module-webif-tpl.c b/module-webif-tpl.c
index 29b93646..66ce02a9 100644
--- a/module-webif-tpl.c
+++ b/module-webif-tpl.c
@@ -455,6 +455,7 @@ char *tpl_getUnparsedTpl(const char *name, int8_t removeHeader, const char *subd
 											check_conf(WITH_SSL, ptr2);
 											check_conf(WITH_STAPI, ptr2);
 											check_conf(WITH_STAPI5, ptr2);
+											check_conf(WITH_EMU, ptr2);
 										} // for
 										if(ok == 0)
 										{
diff --git a/module-webif.c b/module-webif.c
index cbe97391..51c4e640 100644
--- a/module-webif.c
+++ b/module-webif.c
@@ -137,8 +137,9 @@ static bool use_srvid2 = false;
 #define MNU_GBX_FSTAINF     27
 #define MNU_GBX_FEXPINF     28
 #define MNU_GBX_INFOLOG     29
+#define MNU_CFG_FSOFTCAMKEY 30
 
-#define MNU_CFG_TOTAL_ITEMS 30 // sum of items above. Use it for "All inactive" in function calls too.
+#define MNU_CFG_TOTAL_ITEMS 31 // sum of items above. Use it for "All inactive" in function calls too.
 
 static void set_status_info_var(struct templatevars *vars, char *varname, int no_data, char *fmt, double value)
 {
@@ -1328,6 +1329,11 @@ static char *send_oscam_config_streamrelay(struct templatevars *vars, struct uri
 
 	tpl_printf(vars, TPLADD, "STREAM_RELAY_BUFFER_TIME", "%d", cfg.stream_relay_buffer_time);
 	tpl_printf(vars, TPLADD, "STREAM_RELAY_RECONNECT_COUNT", "%d", cfg.stream_relay_reconnect_count);
+#ifdef WITH_EMU
+	tpl_printf(vars, TPLADD, "TMP", "STREAMEMMENABLEDSELECTED%d", cfg.emu_stream_emm_enabled);
+	tpl_addVar(vars, TPLADD, tpl_getVar(vars, "TMP"), "selected");
+	tpl_printf(vars, TPLADD, "STREAM_ECM_DELAY", "%d", cfg.emu_stream_ecm_delay);
+#endif
 
 	tpl_printf(vars, TPLADD, "TMP", "STREAMCONFIGCLIENTSELECTED%d", cfg.stream_display_client);
 	tpl_addVar(vars, TPLADD, tpl_getVar(vars, "TMP"), "selected");
@@ -2441,7 +2447,7 @@ static char *send_oscam_reader_config(struct templatevars *vars, struct uriparam
 		chk_reader("services", servicelabels, rdr);
 		chk_reader("lb_whitelist_services", servicelabelslb, rdr);
 
-		if(is_network_reader(rdr))    //physical readers make trouble if re-started
+		if(is_network_reader(rdr) || rdr->typ == R_EMU)    //physical readers make trouble if re-started
 		{
 			if(rdr)
 				{
@@ -3215,6 +3221,8 @@ static char *send_oscam_reader_config(struct templatevars *vars, struct uriparam
 	tpl_printf(vars, TPLADD, "RESHARE",      "%d", cfg.cc_reshare);
 	tpl_printf(vars, TPLADD, "CCCRECONNECT", "%d", rdr->cc_reconnect);
 
+	if(rdr->cc_want_emu)
+		{ tpl_addVar(vars, TPLADD, "CCCWANTEMUCHECKED", "checked"); }
 	if(rdr->cc_keepalive)
 		{ tpl_addVar(vars, TPLADD, "KEEPALIVECHECKED", "checked"); }
 #endif
@@ -3252,6 +3260,23 @@ static char *send_oscam_reader_config(struct templatevars *vars, struct uriparam
 	tpl_addVar(vars, TPLADD, "USERSCRIPT", rdr->userscript);
 #endif
 
+#ifdef WITH_EMU
+	//emu_auproviders
+	value = mk_t_ftab(&rdr->emu_auproviders);
+	tpl_addVar(vars, TPLADD, "EMUAUPROVIDERS", value);
+	free_mk_t(value);
+
+	// Date-coded BISS keys
+	if(!apicall)
+	{
+		tpl_addVar(vars, TPLADD, "EMUDATECODEDENABLED", (rdr->emu_datecodedenabled == 1) ? "checked" : "");
+	}
+	else
+	{
+		tpl_addVar(vars, TPLADD, "EMUDATECODEDENABLED", (rdr->emu_datecodedenabled == 1) ? "1" : "0");
+	}
+#endif
+
 	tpl_addVar(vars, TPLADD, "PROTOCOL", reader_get_type_desc(rdr, 0));
 
 	// Show only parameters which needed for the reader
@@ -3273,6 +3298,9 @@ static char *send_oscam_reader_config(struct templatevars *vars, struct uriparam
 	case R_CAMD35 :
 		tpl_addVar(vars, TPLAPPEND, "READERDEPENDINGCONFIG", tpl_getTpl(vars, "READERCONFIGCAMD35BIT"));
 		break;
+	case R_EMU :
+		tpl_addVar(vars, TPLAPPEND, "READERDEPENDINGCONFIG", tpl_getTpl(vars, "READERCONFIGEMUBIT"));
+		break;
 	case R_CS378X :
 		tpl_addVar(vars, TPLAPPEND, "READERDEPENDINGCONFIG", tpl_getTpl(vars, "READERCONFIGCS378XBIT"));
 		break;
@@ -5301,9 +5329,34 @@ static char *send_oscam_entitlement(struct templatevars *vars, struct uriparams
 
 					tpl_addVar(vars, TPLAPPEND, "LOGHISTORY", "<BR><BR>New Structure:<BR>");
 					char tbuffer[83];
+#ifdef WITH_EMU
+					char keyBuffer[1024];
+#endif
 					int jsondelimiter = 0;
 					while((item = ll_iter_next(&itr)))
 					{
+#ifdef WITH_EMU
+						if(item->isKey)
+						{
+							tpl_addVar(vars, TPLADD, "ENTSTARTDATE", "");
+							tpl_addVar(vars, TPLADD, "ENTENDDATE", "");
+							cs_hexdump(0, item->key, item->keyLength, keyBuffer, sizeof(keyBuffer));
+							tpl_addVar(vars, TPLADD, "ENTEXPIERED", "e_valid");
+							tpl_printf(vars, TPLADD, "ENTCAID", "%04X", item->caid);
+							tpl_printf(vars, TPLADD, "ENTPROVID", "%08X", item->provid);
+							tpl_addVar(vars, TPLADD, "ENTID", item->name);
+							tpl_addVar(vars, TPLADD, "ENTCLASS", keyBuffer);
+							if(item->isData) { tpl_addVar(vars, TPLADD, "ENTTYPE", "data"); }
+							else { tpl_addVar(vars, TPLADD, "ENTTYPE", "key"); }
+							tpl_addVar(vars, TPLADD, "ENTRESNAME", "");
+
+							if((strcmp(getParam(params, "hideexpired"), "1") != 0) || (item->end > now))
+								{ tpl_addVar(vars, TPLAPPEND, "READERENTENTRY", tpl_getTpl(vars, "ENTITLEMENTITEMBIT")); }
+
+							continue;
+						}
+#endif
+
 						localtime_r(&item->start, &start_t);
 						localtime_r(&item->end, &end_t);
 
@@ -5797,6 +5850,9 @@ static char *send_oscam_status(struct templatevars * vars, struct uriparams * pa
 						filtered = (type == cl->typ) || (type == 'x' && (cl->typ == 'p' || cl->typ == 'r') && (cl->reader && cl->reader->cacheex.mode));
 #else
 						filtered = (type == cl->typ);
+#endif
+#ifdef WITH_EMU
+						if(type == 'e' && cl->typ == 'r' && cl->reader->typ == R_EMU) filtered = 1;
 #endif
 					}
 				}
@@ -7370,6 +7426,9 @@ static char *send_oscam_files(struct templatevars * vars, struct uriparams * par
 		{ "stats.info",      MNU_GBX_FSTAINF,   FTYPE_GBOX },     // id 27
 		{ "expired.info",    MNU_GBX_FEXPINF,   FTYPE_GBOX },     // id 28
 		{ "info.log",        MNU_GBX_INFOLOG,   FTYPE_GBOX },     // id 29
+#endif
+#ifdef WITH_EMU
+		{ "SoftCam.Key",     MNU_CFG_FSOFTCAMKEY,FTYPE_CONFIG },  // id 30
 #endif
 		{ NULL, 0, 0 },
 	};
@@ -7842,7 +7901,11 @@ static char *send_oscam_EMM_running(struct templatevars * vars, struct uriparams
 		else if(!proxy && rdr->csystem_active)     // local active reader
 		{
 			csystem = rdr->csystem;
-			caid = rdr->caid;
+
+			if(rdr->typ != R_EMU)
+			{
+				caid = rdr->caid;
+			}
 		}
 
 		if(csystem)
@@ -9011,7 +9074,11 @@ static int32_t readRequest(FILE * f, IN_ADDR_T in, char **result, int8_t forcePl
 		memcpy(*result + bufsize, buf2, n);
 		bufsize += n;
 
+#ifdef WITH_EMU
+		if(bufsize > 204800) // max request size 200kb
+#else
 		if(bufsize > 102400) // max request size 100kb
+#endif
 		{
 			cs_log("error: too much data received from %s", cs_inet_ntoa(in));
 			NULLFREE(*result);
diff --git a/oscam-chk.c b/oscam-chk.c
index 624d1d3f..1b54442a 100644
--- a/oscam-chk.c
+++ b/oscam-chk.c
@@ -894,7 +894,7 @@ int32_t matching_reader(ECM_REQUEST *er, struct s_reader *rdr)
 		return 0;
 	}
 
-	if( !is_network_reader(rdr) && (rdr->caid >> 8 != ((er->caid >> 8) & 0xFF)) && (rdr->caid >> 8 != ((er->ocaid >> 8) & 0xFF)) )
+	if(!(rdr->typ == R_EMU) && !is_network_reader(rdr) && ((rdr->caid >> 8) != ((er->caid >> 8) & 0xFF) && (rdr->caid >> 8) != ((er->ocaid >> 8) & 0xFF)))
 	{
 		if (!rdr->csystem)
 			{ return 0; }
@@ -932,7 +932,7 @@ int32_t matching_reader(ECM_REQUEST *er, struct s_reader *rdr)
 	}
 
 	// Checking ident:
-	if(!chk_rfilter(er, rdr))
+	if(!(rdr->typ == R_EMU && caid_is_biss(er->caid)) && !chk_rfilter(er, rdr))
 	{
 		cs_log_dbg(D_TRACE, "r-filter reader %s", rdr->label);
 		return (0);
@@ -993,7 +993,7 @@ int32_t matching_reader(ECM_REQUEST *er, struct s_reader *rdr)
 	}
 
 	// Checking entitlements:
-	if(ll_count(rdr->ll_entitlements) > 0)
+	if(ll_count(rdr->ll_entitlements) > 0 && !(rdr->typ == R_EMU))
 	{
 		LL_ITER itr = ll_iter_create(rdr->ll_entitlements);
 		S_ENTITLEMENT *item;
@@ -1174,7 +1174,7 @@ int32_t chk_caid(uint16_t caid, CAIDTAB *ctab)
 
 int32_t chk_caid_rdr(struct s_reader *rdr, uint16_t caid)
 {
-	if(is_network_reader(rdr))
+	if(is_network_reader(rdr) || rdr->typ == R_EMU)
 	{
 		return 1; // reader caid is not real caid
 	}
diff --git a/oscam-config-global.c b/oscam-config-global.c
index 508d8e58..a946e2a9 100644
--- a/oscam-config-global.c
+++ b/oscam-config-global.c
@@ -940,6 +940,10 @@ static const struct config_list streamrelay_opts[] =
 	DEF_OPT_STR("stream_source_auth_password"    , OFS(stream_source_auth_password),  NULL),
 	DEF_OPT_UINT32("stream_relay_buffer_time"    , OFS(stream_relay_buffer_time),     0),
 	DEF_OPT_UINT8("stream_relay_reconnect_count" , OFS(stream_relay_reconnect_count), 0),
+#ifdef WITH_EMU
+	DEF_OPT_INT8("stream_emm_enabled"            , OFS(emu_stream_emm_enabled),       0),
+	DEF_OPT_UINT32("stream_ecm_delay"            , OFS(emu_stream_ecm_delay),         600),
+#endif
 	DEF_OPT_INT8("stream_display_client"         , OFS(stream_display_client),        0),
 	DEF_OPT_INT8("stream_reuse_client"           , OFS(stream_reuse_client),          0),
 #ifdef WEBIF
diff --git a/oscam-config-reader.c b/oscam-config-reader.c
index 786b3198..b3e98dbf 100644
--- a/oscam-config-reader.c
+++ b/oscam-config-reader.c
@@ -114,6 +114,7 @@ static void protocol_fn(const char *token, char *value, void *setting, FILE *f)
 			{ "newcamd525", R_NEWCAMD },
 			{ "newcamd524", R_NEWCAMD },
 			{ "drecas",     R_DRECAS },
+			{ "emu",        R_EMU },
 			{ NULL,         0 }
 		}, *p;
 		int i;
@@ -682,6 +683,9 @@ void ftab_fn(const char *token, char *value, void *setting, long ftab_type, FILE
 		if(ftab_type & FTAB_FBPCAID)     { rdr = container_of(setting, struct s_reader, fallback_percaid); }
 		if(ftab_type & FTAB_LOCALCARDS)  { rdr = container_of(setting, struct s_reader, localcards); }
 		if(ftab_type & FTAB_IGNCHKSMCAID){ rdr = container_of(setting, struct s_reader, disablecrccws_only_for); }
+#ifdef WITH_EMU
+		if(ftab_type & FTAB_EMUAU)       { rdr = container_of(setting, struct s_reader, emu_auproviders); }
+#endif
 #ifdef MODULE_GBOX
 		if(ftab_type & FTAB_CCCGBXRESHARE){ rdr = container_of(setting, struct s_reader, ccc_gbx_reshare_ident); }
 #endif
@@ -1228,6 +1232,7 @@ static const struct config_list reader_opts[] =
 	DEF_OPT_SSTR("cccversion"                     , OFS(cc_version),                      "", SIZEOF(cc_version)),
 	DEF_OPT_INT8("cccmaxhops"                     , OFS(cc_maxhops),                      DEFAULT_CC_MAXHOPS),
 	DEF_OPT_INT8("cccmindown"                     , OFS(cc_mindown),                      0),
+	DEF_OPT_INT8("cccwantemu"                     , OFS(cc_want_emu),                     0),
 	DEF_OPT_INT8("ccckeepalive"                   , OFS(cc_keepalive),                    DEFAULT_CC_KEEPALIVE),
 	DEF_OPT_INT8("cccreshare"                     , OFS(cc_reshare),                      DEFAULT_CC_RESHARE),
 	DEF_OPT_INT32("cccreconnect"                  , OFS(cc_reconnect),                    DEFAULT_CC_RECONNECT),
@@ -1242,6 +1247,10 @@ static const struct config_list reader_opts[] =
 #endif
 #ifdef READER_DRECAS
 	DEF_OPT_STR("stmkeys"                         , OFS(stmkeys),                         NULL),
+#endif
+#ifdef WITH_EMU
+	DEF_OPT_FUNC_X("emu_auproviders"              , OFS(emu_auproviders),                ftab_fn, FTAB_READER | FTAB_EMUAU),
+	DEF_OPT_INT8("emu_datecodedenabled"           , OFS(emu_datecodedenabled),           0),
 #endif
 	DEF_OPT_INT8("resetalways"                    , OFS(resetalways),                     0),
 	DEF_OPT_INT8("deprecated"                     , OFS(deprecated),                      0),
@@ -1338,7 +1347,7 @@ static bool reader_check_setting(const struct config_list *UNUSED(clist), void *
 	// These are written only when the reader is CCCAM
 	static const char *cccam_settings[] =
 	{
-		"cccversion", "cccmaxhops", "cccmindown", "ccckeepalive",
+		"cccversion", "cccmaxhops", "cccmindown", "cccwantemu", "ccckeepalive",
 		"cccreconnect",
 		0
 	};
diff --git a/oscam-config.h b/oscam-config.h
index b0179dd8..e66a1aa1 100644
--- a/oscam-config.h
+++ b/oscam-config.h
@@ -64,6 +64,7 @@ enum ftab_fn
 	FTAB_LOCALCARDS            = 0x20,
 	FTAB_IGNCHKSMCAID          = 0x40,
 	FTAB_IGNCRCCEX4USERONLYFOR = 0x80,
+	FTAB_EMUAU                 = 0x100,
 	FTAB_CCCGBXRESHARE         = 0x200
 };
 
diff --git a/oscam-ecm.c b/oscam-ecm.c
index cadd2de9..9ded1fbd 100644
--- a/oscam-ecm.c
+++ b/oscam-ecm.c
@@ -1817,6 +1817,10 @@ int32_t write_ecm_answer(struct s_reader *reader, ECM_REQUEST *er, int8_t rc, ui
 		return 0;
 	}
 
+	// Special checks for rc
+	// Skip check for BISS1 - cw could be zero but still catch cw=0 by anticascading
+	// Skip check for BISS2 - we use the extended cw, so the "simple" cw is always zero
+
 	// bad/wrong chksum/ecm
 	if(rc == E_NOTFOUND && rcEx == E2_WRONG_CHKSUM)
 	{
@@ -1836,7 +1840,7 @@ int32_t write_ecm_answer(struct s_reader *reader, ECM_REQUEST *er, int8_t rc, ui
 		}
 	}
 
-	if(rc < E_NOTFOUND && cw && chk_is_null_CW(cw))
+	if(rc < E_NOTFOUND && cw && chk_is_null_CW(cw) && !caid_is_biss(er->caid))
 	{
 		rc = E_NOTFOUND;
 		cs_log_dbg(D_TRACE | D_LB, "WARNING: reader %s send fake cw, set rc=E_NOTFOUND!", reader ? reader->label : "-");
@@ -2023,7 +2027,9 @@ int32_t write_ecm_answer(struct s_reader *reader, ECM_REQUEST *er, int8_t rc, ui
 	if(!ea->is_pending) // not for pending ea - only once for ea
 	{
 		// cache update
-		if(ea && (ea->rc < E_NOTFOUND) && !chk_is_null_CW(ea->cw))
+		// Skip check for BISS1 - cw could be indeed zero
+		// Skip check for BISS2 - we use the extended cw, so the "simple" cw is always zero
+		if(ea && (ea->rc < E_NOTFOUND) && (!chk_is_null_CW(ea->cw) && !caid_is_biss(er->caid)))
 		{
 #ifdef CS_CACHEEX_AIO
 			int32_t ecmtime = ea->ecm_time;
@@ -2666,7 +2672,9 @@ void get_cw(struct s_client *client, ECM_REQUEST *er)
 	}
 
 	// Check for odd/even byte
-	if(get_odd_even(er) == 0)
+	// Don't check for BISS1 and BISS2 mode 1/E or fake caid (ECM is fake for them)
+	// Don't check for BISS2 mode CA (ECM table is always 0x80)
+	if(!caid_is_biss(er->caid) && !caid_is_fake(er->caid) && get_odd_even(er) == 0)
 	{
 		cs_log_dbg(D_TRACE, "warning: ecm with null odd/even byte from %s", (check_client(er->client) ? er->client->account->usr : "-"));
 		er->rc = E_INVALID;
diff --git a/oscam-emm.c b/oscam-emm.c
index f91c0daa..1d3e1e3a 100644
--- a/oscam-emm.c
+++ b/oscam-emm.c
@@ -40,6 +40,7 @@ static int8_t cs_emmlen_is_blocked(struct s_reader *rdr, int16_t len)
 static int8_t do_simple_emm_filter(struct s_reader *rdr, const struct s_cardsystem *csystem, EMM_PACKET *ep, int8_t cl_dvbapi)
 {
 	if(is_network_reader(rdr)) { return 1; } // don't evaluate on network readers, server with local reader will check it
+	if(rdr->typ == R_EMU) { return 1; } // don't evalutate on emu reader
 
 	//copied and enhanced from module-dvbapi.c
 	//dvbapi_start_emm_filter()
@@ -237,6 +238,24 @@ int32_t emm_reader_match(struct s_reader *reader, uint16_t caid, uint32_t provid
 		rdr_log_dbg(reader, D_EMM, "reader auprovid = %06X fixup to %06X (ignoring last digit)", reader->auprovid, prid);
 	}
 
+#ifdef WITH_EMU
+	if(reader->typ == R_EMU)
+	{
+		FILTER *emu_provids = get_emu_prids_for_caid(reader, caid);
+		if(emu_provids != NULL)
+		{
+			for(i = 0; i < emu_provids->nprids; i++)
+			{
+				if(provid == emu_provids->prids[i])
+				{
+					return 1;
+				}
+			}
+		}
+		return 0;
+	}
+#endif
+
 	if(prid == provid)
 	{
 		rdr_log_dbg(reader, D_EMM, "reader auprovid = %06X matching with emm provid = %06X -> SEND!", prid, provid);
diff --git a/oscam-simples.c b/oscam-simples.c
index 576804c9..6e1c7137 100644
--- a/oscam-simples.c
+++ b/oscam-simples.c
@@ -468,6 +468,8 @@ const char *get_cardsystem_desc_by_caid(uint16_t caid)
 	if(caid_is_viaccess(caid)) { return "viaccess"; }
 	if(caid_is_irdeto(caid)) { return "irdeto"; }
 	if(caid_is_videoguard(caid)) { return "videoguard"; }
+	if(caid_is_powervu(caid)) { return "powervu"; }
+	if(caid_is_director(caid)) { return "director"; }
 	if(caid_is_conax(caid)) { return "conax"; }
 	if(caid_is_cryptoworks(caid)) { return "cryptoworks"; }
 	if(caid_is_betacrypt(caid)) { return "betacrypt"; }
@@ -476,6 +478,7 @@ const char *get_cardsystem_desc_by_caid(uint16_t caid)
 	if(caid >= 0x5501 && caid <= 0x551A) { return "griffin"; }
 	if(caid_is_dre(caid)) { return "drecrypt"; }
 	if(caid_is_bulcrypt(caid)) { return "bulcrypt"; }
+	if(caid_is_biss(caid)) { return "biss"; }
 	if(caid == 0x4ABF) { return "dgcrypt"; }
 	return "unknown";
 }
diff --git a/oscam.c b/oscam.c
index 6324f106..ce511fcf 100644
--- a/oscam.c
+++ b/oscam.c
@@ -41,6 +41,10 @@
 #include "reader-common.h"
 #include "module-gbox.h"
 
+#ifdef WITH_EMU
+	void add_emu_reader(void);
+#endif
+
 #ifdef WITH_SSL
 #include <openssl/crypto.h>
 #include <openssl/ssl.h>
@@ -460,6 +464,8 @@ static void write_versionfile(bool use_stdout)
 #ifdef WITH_SIGNING
 	write_conf(WITH_SIGNING, "Binary signing support");
 #endif
+	write_conf(WITH_EMU, "Emulator support");
+	write_conf(WITH_SOFTCAM, "Built-in SoftCam.Key");
 
 	fprintf(fp, "\n");
 	write_conf(MODULE_CAMD33, "camd 3.3x");
@@ -1676,6 +1682,9 @@ const struct s_cardreader *cardreaders[] =
 #ifdef CARDREADER_STINGER
 	&cardreader_stinger,
 #endif
+#ifdef WITH_EMU
+	&cardreader_emu,
+#endif
 
 	NULL
 };
@@ -1857,6 +1866,9 @@ int32_t main(int32_t argc, char *argv[])
 
 	init_sidtab();
 	init_readerdb();
+#ifdef WITH_EMU
+	add_emu_reader();
+#endif
 	cfg.account = init_userdb();
 	init_signal();
 	init_provid();
diff --git a/reader-common.c b/reader-common.c
index efdd19b3..2c8fc7dd 100644
--- a/reader-common.c
+++ b/reader-common.c
@@ -15,6 +15,7 @@
 #include "reader-common.h"
 //#include "csctapi/atr.h"
 #include "csctapi/icc_async.h"
+#include "readers.h" // required by the EMU reader
 
 extern const struct s_cardsystem *cardsystems[];
 extern char *RDR_CD_TXT[];
@@ -150,6 +151,18 @@ static int32_t reader_get_cardsystem(struct s_reader *reader, ATR *atr)
 {
 	int32_t i;
 
+#ifdef WITH_EMU
+	if(reader->typ == R_EMU)
+	{
+		NULLFREE(reader->csystem_data);
+		rdr_log(reader, "found card system %s", reader_emu.desc);
+		reader->csystem = &reader_emu;
+		reader->csystem_active = true;
+		led_status_found_cardsystem();
+		return (reader->csystem_active);
+	}
+#endif
+
 	for(i = 0; cardsystems[i]; i++)
 	{
 		NULLFREE(reader->csystem_data);
diff --git a/readers.h b/readers.h
index eec350e9..316c2bea 100644
--- a/readers.h
+++ b/readers.h
@@ -17,5 +17,6 @@ extern const struct s_cardsystem reader_tongfang;
 extern const struct s_cardsystem reader_bulcrypt;
 extern const struct s_cardsystem reader_griffin;
 extern const struct s_cardsystem reader_dgcrypt;
+extern const struct s_cardsystem reader_emu;
 
 #endif
diff --git a/webif/config/streamrelay.html b/webif/config/streamrelay.html
index f79d5a1c..3804d00f 100644
--- a/webif/config/streamrelay.html
+++ b/webif/config/streamrelay.html
@@ -27,6 +27,7 @@
 			<TR><TD><A>Source Stream Password:</A></TD><TD><input name="stream_source_auth_password" type="text" value="##STREAM_SOURCE_AUTH_PASSWORD##"></TD></TR>
 			<TR><TD><A>Relay Buffer Time:</A></TD><TD><input name="stream_relay_buffer_time" class="short" type="text" maxlength="5" value="##STREAM_RELAY_BUFFER_TIME##"><label> ms (delay for stream processing)</label></TD></TR>
 			<TR><TD><A>Relay Reconnect Count:</A></TD><TD><input name="stream_relay_reconnect_count" class="short" type="text" maxlength="2" value="##STREAM_RELAY_RECONNECT_COUNT##"><label> attempts until an interrupted stream is disconnected (0 = disabled)</label></TD></TR>
+##TPLSTREAMEMUSETTINGS##
 			<TR><TD><A>Relay Client Display Options:</A></TD>
 				<TD CLASS="invisible">
 					<TABLE>
diff --git a/webif/config/streamrelay_emusettings.html b/webif/config/streamrelay_emusettings.html
new file mode 100644
index 00000000..dcb0618b
--- /dev/null
+++ b/webif/config/streamrelay_emusettings.html
@@ -0,0 +1,9 @@
+			<TR><TD><A>Process EMM from stream (emu):</A></TD>
+				<TD>
+					<select name="stream_emm_enabled">
+						<option value="0" ##STREAMEMMENABLEDSELECTED0##>0 - disabled</option>
+						<option value="1" ##STREAMEMMENABLEDSELECTED1##>1 - enabled</option>
+					</select>
+				</TD>
+			</TR>
+			<TR><TD><A>ECM fix delay (emu):</A></TD><TD><input name="stream_ecm_delay" class="short" type="text" maxlength="5" value="##STREAM_ECM_DELAY##"><label> ms (delay for PowerVU processing)</label></TD></TR>
diff --git a/webif/files/menu.html b/webif/files/menu.html
index 4dc6f02e..2e4e6b62 100644
--- a/webif/files/menu.html
+++ b/webif/files/menu.html
@@ -10,7 +10,7 @@
 		<LI CLASS="##CMENUACTIVE7##"><A HREF="files.html?file=logfile">logfile</A></LI>
 		<LI CLASS="##CMENUACTIVE8##"><A HREF="files.html?file=userfile">userfile</A></LI>
 ##TPLFILEMENUGBOX##		<!-- CMENUACTIVE19-29 -->
-		<LI CLASS="##CMENUACTIVE9## ##CMENUACTIVE10## ##CMENUACTIVE11## ##CMENUACTIVE12## ##CMENUACTIVE13## ##CMENUACTIVE14## ##CMENUACTIVE15## ##CMENUACTIVE16## ##CMENUACTIVE17## ##CMENUACTIVE18##"><A HREF="#"  class="drop">other files<b class="subcaret"></b></A>
+		<LI CLASS="##CMENUACTIVE9## ##CMENUACTIVE10## ##CMENUACTIVE11## ##CMENUACTIVE12## ##CMENUACTIVE13## ##CMENUACTIVE14## ##CMENUACTIVE15## ##CMENUACTIVE16## ##CMENUACTIVE17## ##CMENUACTIVE18## ##CMENUACTIVE30##"><A HREF="#"  class="drop">other files<b class="subcaret"></b></A>
 			<UL CLASS="dropdown_nav">
 				<LI CLASS="##CMENUACTIVE9##"><A HREF="files.html?file=oscam.services">oscam.services</A></LI>
 				<LI CLASS="##CMENUACTIVE10##"><A HREF="files.html?file=oscam.provid">oscam.provid</A></LI>
@@ -22,6 +22,7 @@
 ##VIEW_FILEMENUCSS##		<!-- CMENUACTIVE16 -->
 ##TPLFILEMENUTWIN##		<!-- CMENUACTIVE17 -->
 ##TPLFILEMENUCONSTCW##  	<!-- CMENUACTIVE18 -->
+##TPLFILEMENUSOFTCAMKEY##		<!-- CMENUACTIVE30 -->
 			</UL>
 		</LI>
 	</UL>
diff --git a/webif/files/menu_softcamkey.html b/webif/files/menu_softcamkey.html
new file mode 100644
index 00000000..cf718779
--- /dev/null
+++ b/webif/files/menu_softcamkey.html
@@ -0,0 +1 @@
+			<LI CLASS="##CMENUACTIVE30##"><A HREF="files.html?file=SoftCam.Key">SoftCam.Key</A></LI>
diff --git a/webif/pages_index.txt b/webif/pages_index.txt
index a104089b..fd1c857e 100644
--- a/webif/pages_index.txt
+++ b/webif/pages_index.txt
@@ -1,10 +1,10 @@
 # This file contains index of the templates.
-# The format is simple "TEMPLATE_NAME  FILENAME  DEPENDENCY1,DEPENDENCYx"
+# The format is simple "TEMPLATE_NAME  FILENAME  DEPENDENCY1,DEPENDANCYx"
 # Lines starting with # are ignorred
 #  TEMPLATE_NAME - name of the template and also name of the file that
 #                  contains the template
 #  FILENAME      - The file that contains the template
-#  DEPENDENCYx   - The config variable which is responsible for this template
+#  DEPENDANCYx   - The config variable which is responsible for this template
 
 JSONCACHEEX                   api.json/cacheex.json                                       CS_CACHEEX
 JSONCACHEEXBIT                api.json/cacheexbit.json                                    CS_CACHEEX
@@ -62,7 +62,7 @@ CONFIGCCCAM                   config/cccam.html
 CONFIGCCCAMCTRL               config/cccam_control.html                                   MODULE_CCCSHARE
 CONFIGCONTENT                 config/config.html
 CONFIGDVBAPI                  config/dvbapi.html                                          HAVE_DVBAPI
-EXTENDEDCWAPI                 config/dvbapi_extended_cw_api.html                          WITH_EXTENDED_CW
+EXTENDEDCWAPI                 config/dvbapi_extended_cw_api.html                          WITH_EXTENDED_CW,WITH_EMU
 DEMUXERFIX                    config/dvbapi_demuxerfix.html                               MODULE_STREAMRELAY
 CCCAMRESHAREBIT               config/cccreshare.html                                      MODULE_GBOX
 CONFIGGBOX                    config/gbox.html                                            MODULE_GBOX
@@ -92,6 +92,7 @@ CONFIGMENUNEWCAMD             config/menu_newcamd.html
 CONFIGMENURADEGAST            config/menu_radegast.html                                   MODULE_RADEGAST
 CONFIGMENUSCAM                config/menu_scam.html                                       MODULE_SCAM
 CONFIGMENUSTREAMRELAY         config/menu_streamrelay.html                                MODULE_STREAMRELAY
+STREAMEMUSETTINGS             config/streamrelay_emusettings.html                         WITH_EMU
 CONFIGMENUSERIAL              config/menu_serial.html                                     MODULE_SERIAL
 CONFIGMONITOR                 config/monitor.html                                         MODULE_MONITOR
 CONFIGNEWCAMD                 config/newcamd.html                                         MODULE_NEWCAMD
@@ -131,6 +132,7 @@ FILEMENUDVBAPI                files/menu_dvbapi.html
 FILEMENUFAKECWS               files/menu_fakecws.html                                     CS_CACHEEX
 FILEMENUGBOX                  files/menu_gbox.html                                        MODULE_GBOX
 FILEMENUTWIN                  files/menu_twin.html                                        MODULE_SERIAL
+FILEMENUSOFTCAMKEY            files/menu_softcamkey.html                                  WITH_EMU
 
 AUTOCONF                      ghttp/autoconf.html                                         MODULE_GHTTP
 PREAUTOCONF                   ghttp/pre_autoconf.html                                     MODULE_GHTTP
@@ -199,6 +201,7 @@ READEREDITCACHEEXBIT          readerconfig/readerconfig_cacheexbit.html
 READEREDITCACHEEXAIOBIT       readerconfig/readerconfig_cacheexaiobit.html                CS_CACHEEX_AIO
 READERCONFIGCAMD35BIT         readerconfig/readerconfig_camd35bit.html                    MODULE_CAMD35
 READERCONFIGCCCAMBIT          readerconfig/readerconfig_cccambit.html                     MODULE_CCCAM
+READERCONFIGEMUBIT            readerconfig/readerconfig_emubit.html                       WITH_EMU
 READERCONFIGCS378XBIT         readerconfig/readerconfig_cs378xbit.html                    MODULE_CAMD35_TCP
 READERCONFIGGBOXBIT           readerconfig/readerconfig_gboxbit.html                      MODULE_GBOX
 READERINFOGBOXREMM            readerconfig/readerinfo_gbox_remm.html                      MODULE_GBOX
diff --git a/webif/readerconfig/readerconfig_cccambit.html b/webif/readerconfig/readerconfig_cccambit.html
index 3812bec7..85232402 100644
--- a/webif/readerconfig/readerconfig_cccambit.html
+++ b/webif/readerconfig/readerconfig_cccambit.html
@@ -22,4 +22,5 @@
 			<TR><TD><A>Maxhop:</A></TD><TD><input name="cccmaxhops" class="short" type="text" maxlength="2" value="##CCCMAXHOPS##"></TD></TR>
 			<TR><TD><A>Mindown:</A></TD><TD><input name="cccmindown" class="short" type="text" maxlength="2" value="##CCCMINDOWN##"></TD></TR>
 			<TR><TD><A>Reshare:</A></TD><TD><input name="cccreshare" class="short" type="text" maxlength="2" value="##CCCRESHARE##">				&nbsp;Global CCCam Reshare:<SPAN CLASS="global_conf" TITLE="This value is used if Reshare = -1"><A HREF="config.html?part=cccam">##RESHARE##</A></SPAN></TD></TR>
+			<TR><TD><A>Want Emu:</A></TD><TD><input name="cccwantemu" type="hidden" value="0"><input name="cccwantemu" type="checkbox" value="1" ##CCCWANTEMUCHECKED##><label></label></TD></TR>
 			<TR><TD><A>Keep alive:</A></TD><TD><input name="ccckeepalive" type="hidden" value="0"><input name="ccckeepalive" type="checkbox" value="1" ##KEEPALIVECHECKED##><label></label></TD></TR>
diff --git a/webif/readerconfig/readerconfig_emubit.html b/webif/readerconfig/readerconfig_emubit.html
new file mode 100644
index 00000000..8c130b7c
--- /dev/null
+++ b/webif/readerconfig/readerconfig_emubit.html
@@ -0,0 +1,2 @@
+			<TR><TD><A>AU providers:</A></TD><TD><textarea name="emu_auproviders" rows="3" class="bt">##EMUAUPROVIDERS##</textarea></TD></TR>
+			<TR><TD><A>[BISS] Enable date-coded keys:</A></TD><TD><input name="emu_datecodedenabled" type="hidden" value="0"><input name="emu_datecodedenabled" type="checkbox" value="1" ##EMUDATECODEDENABLED##><label></label></TD></TR>
