diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index a5825e15e7..9b6934cd89 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -135,6 +135,7 @@ set(SRCS ActionReplay.cpp
HW/WiimoteEmu/Speaker.cpp
HW/WiimoteReal/WiimoteReal.cpp
HW/WiiSaveCrypted.cpp
+ IPC_HLE/ESFormats.cpp
IPC_HLE/ICMPLin.cpp
IPC_HLE/NWC24Config.cpp
IPC_HLE/WII_IPC_HLE.cpp
diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj
index d18d130422..7f4b1b3208 100644
--- a/Source/Core/Core/Core.vcxproj
+++ b/Source/Core/Core/Core.vcxproj
@@ -167,6 +167,7 @@
+
@@ -392,6 +393,7 @@
+
diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters
index 4b33dd2e2e..37653a0520 100644
--- a/Source/Core/Core/Core.vcxproj.filters
+++ b/Source/Core/Core/Core.vcxproj.filters
@@ -565,6 +565,9 @@
DSPCore
+
+ IPC HLE %28IOS/Starlet%29\ES
+
IPC HLE %28IOS/Starlet%29
@@ -1162,6 +1165,9 @@
DSPCore
+
+ IPC HLE %28IOS/Starlet%29\ES
+
IPC HLE %28IOS/Starlet%29
diff --git a/Source/Core/Core/IPC_HLE/ESFormats.cpp b/Source/Core/Core/IPC_HLE/ESFormats.cpp
new file mode 100644
index 0000000000..7cd56c14c4
--- /dev/null
+++ b/Source/Core/Core/IPC_HLE/ESFormats.cpp
@@ -0,0 +1,96 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "Core/IPC_HLE/ESFormats.h"
+
+#include
+#include
+#include
+
+#include "Common/ChunkFile.h"
+#include "Common/CommonFuncs.h"
+#include "Common/CommonTypes.h"
+
+TMDReader::TMDReader(const std::vector& bytes) : m_bytes(bytes)
+{
+}
+
+TMDReader::TMDReader(std::vector&& bytes) : m_bytes(std::move(bytes))
+{
+}
+
+void TMDReader::SetBytes(const std::vector& bytes)
+{
+ m_bytes = bytes;
+}
+
+void TMDReader::SetBytes(std::vector&& bytes)
+{
+ m_bytes = std::move(bytes);
+}
+
+bool TMDReader::IsValid() const
+{
+ if (m_bytes.size() < 0x1E4)
+ {
+ // TMD is too small to contain its base fields.
+ return false;
+ }
+
+ if (m_bytes.size() < 0x1E4 + GetNumContents() * 36u)
+ {
+ // TMD is too small to contain all its expected content entries.
+ return false;
+ }
+
+ return true;
+}
+
+u64 TMDReader::GetTitleId() const
+{
+ return Common::swap64(m_bytes.data() + 0x18C);
+}
+
+u16 TMDReader::GetNumContents() const
+{
+ return Common::swap16(m_bytes.data() + 0x1DE);
+}
+
+bool TMDReader::GetContent(u16 index, Content* content) const
+{
+ if (index >= GetNumContents())
+ {
+ return false;
+ }
+
+ const u8* content_base = m_bytes.data() + 0x1E4 + index * 36;
+ content->id = Common::swap32(content_base);
+ content->index = Common::swap16(content_base + 4);
+ content->type = Common::swap16(content_base + 6);
+ content->size = Common::swap64(content_base + 8);
+ std::copy(content_base + 16, content_base + 36, content->sha1.begin());
+
+ return true;
+}
+
+bool TMDReader::FindContentById(u32 id, Content* content) const
+{
+ for (u16 index = 0; index < GetNumContents(); ++index)
+ {
+ if (!GetContent(index, content))
+ {
+ return false;
+ }
+ if (content->id == id)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+void TMDReader::DoState(PointerWrap& p)
+{
+ p.Do(m_bytes);
+}
diff --git a/Source/Core/Core/IPC_HLE/ESFormats.h b/Source/Core/Core/IPC_HLE/ESFormats.h
new file mode 100644
index 0000000000..74f023244b
--- /dev/null
+++ b/Source/Core/Core/IPC_HLE/ESFormats.h
@@ -0,0 +1,46 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+// Utilities to manipulate files and formats from the Wii's ES module: tickets,
+// TMD, and other title informations.
+
+#pragma once
+
+#include
+#include
+
+#include "Common/ChunkFile.h"
+#include "Common/CommonTypes.h"
+
+class TMDReader final
+{
+public:
+ TMDReader() = default;
+ explicit TMDReader(const std::vector& bytes);
+ explicit TMDReader(std::vector&& bytes);
+
+ void SetBytes(const std::vector& bytes);
+ void SetBytes(std::vector&& bytes);
+
+ bool IsValid() const;
+
+ u64 GetTitleId() const;
+
+ struct Content
+ {
+ u32 id;
+ u16 index;
+ u16 type;
+ u64 size;
+ std::array sha1;
+ };
+ u16 GetNumContents() const;
+ bool GetContent(u16 index, Content* content) const;
+ bool FindContentById(u32 id, Content* content) const;
+
+ void DoState(PointerWrap& p);
+
+private:
+ std::vector m_bytes;
+};