aboutsummaryrefslogtreecommitdiff
path: root/Src/replicant/nsapev2
diff options
context:
space:
mode:
Diffstat (limited to 'Src/replicant/nsapev2')
-rw-r--r--Src/replicant/nsapev2/VERSION1
-rw-r--r--Src/replicant/nsapev2/flags.h20
-rw-r--r--Src/replicant/nsapev2/header.cpp91
-rw-r--r--Src/replicant/nsapev2/header.h39
-rw-r--r--Src/replicant/nsapev2/item.cpp235
-rw-r--r--Src/replicant/nsapev2/item.h43
-rw-r--r--Src/replicant/nsapev2/nsapev2.h75
-rw-r--r--Src/replicant/nsapev2/nsapev2.sln81
-rw-r--r--Src/replicant/nsapev2/nsapev2.vcxproj172
-rw-r--r--Src/replicant/nsapev2/nsapev2_common.cpp465
-rw-r--r--Src/replicant/nsapev2/precomp.h26
-rw-r--r--Src/replicant/nsapev2/tag.cpp285
-rw-r--r--Src/replicant/nsapev2/tag.h50
-rw-r--r--Src/replicant/nsapev2/util.h1
-rw-r--r--Src/replicant/nsapev2/windows/nsapev2.cpp1
-rw-r--r--Src/replicant/nsapev2/windows/nsapev2.h20
16 files changed, 1605 insertions, 0 deletions
diff --git a/Src/replicant/nsapev2/VERSION b/Src/replicant/nsapev2/VERSION
new file mode 100644
index 00000000..ea710abb
--- /dev/null
+++ b/Src/replicant/nsapev2/VERSION
@@ -0,0 +1 @@
+1.2 \ No newline at end of file
diff --git a/Src/replicant/nsapev2/flags.h b/Src/replicant/nsapev2/flags.h
new file mode 100644
index 00000000..621857f1
--- /dev/null
+++ b/Src/replicant/nsapev2/flags.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "foundation/error.h"
+namespace APEv2
+{
+ /*
+ http://wiki.hydrogenaudio.org/index.php?title=Ape_Tags_Flags
+ */
+ enum
+ {
+ /* flags for header or item */
+ FLAG_READONLY = 1,
+
+ /* header/footer specific flags */
+ FLAG_HEADER_HAS_HEADER = (1 << 31),
+ FLAG_HEADER_NO_FOOTER = (1 << 30),
+ FLAG_HEADER_IS_HEADER = (1 << 29),
+ FLAG_HEADER_ENCODE_MASK = FLAG_READONLY|FLAG_HEADER_HAS_HEADER|FLAG_HEADER_NO_FOOTER,
+ };
+
+}
diff --git a/Src/replicant/nsapev2/header.cpp b/Src/replicant/nsapev2/header.cpp
new file mode 100644
index 00000000..eee96b9a
--- /dev/null
+++ b/Src/replicant/nsapev2/header.cpp
@@ -0,0 +1,91 @@
+#include "header.h"
+#include "flags.h"
+#include "util.h"
+#include "nu/ByteReader.h"
+#include "nu/ByteWriter.h"
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+static char apev2_preamble[] = { 'A', 'P', 'E', 'T', 'A', 'G', 'E', 'X' };
+APEv2::Header::Header()
+{
+ memcpy(preamble, apev2_preamble, 8);
+ version=2000;
+ size=0;
+ items=0;
+ flags=0;
+ reserved=0;
+}
+
+APEv2::Header::Header(const void *data)
+{
+ bytereader_value_t byte_reader;
+ bytereader_init(&byte_reader, data, Header::SIZE);
+
+ bytereader_read_n(&byte_reader, preamble, 8);
+ version=bytereader_read_u32_le(&byte_reader);
+ size=bytereader_read_u32_le(&byte_reader);
+ items=bytereader_read_u32_le(&byte_reader);
+ flags=bytereader_read_u32_le(&byte_reader);
+ bytereader_read_n(&byte_reader, &reserved, 8);
+}
+
+uint32_t APEv2::Header::GetFlags() const
+{
+ return flags;
+}
+
+bool APEv2::Header::Valid() const
+{
+ return !memcmp(preamble, apev2_preamble, 8) && reserved == 0;
+}
+
+uint32_t APEv2::Header::TagSize() const
+{
+ size_t size = this->size;
+ if (IsHeader() && HasFooter())
+ size+=SIZE;
+ if (IsFooter() && HasHeader())
+ size+=SIZE;
+
+ if (size > ULONG_MAX)
+ return 0;
+
+ return (uint32_t)size;
+}
+
+bool APEv2::Header::HasHeader() const
+{
+ return !!(flags & FLAG_HEADER_HAS_HEADER);
+}
+
+bool APEv2::Header::HasFooter() const
+{
+ return !(flags & FLAG_HEADER_NO_FOOTER);
+}
+
+bool APEv2::Header::IsFooter() const
+{
+ return !(flags & FLAG_HEADER_IS_HEADER);
+}
+
+bool APEv2::Header::IsHeader() const
+{
+ return !!(flags & FLAG_HEADER_IS_HEADER);
+}
+
+int APEv2::Header::Encode(bytewriter_t byte_writer) const
+{
+ if (bytewriter_size(byte_writer) < 32)
+ return NErr_Insufficient;
+
+ bytewriter_write_n(byte_writer, apev2_preamble, 8);
+ bytewriter_write_u32_le(byte_writer, version);
+ bytewriter_write_u32_le(byte_writer, size);
+ bytewriter_write_u32_le(byte_writer, items);
+ bytewriter_write_u32_le(byte_writer, flags);
+ bytewriter_write_zero_n(byte_writer, 8);
+
+ return NErr_Success;
+} \ No newline at end of file
diff --git a/Src/replicant/nsapev2/header.h b/Src/replicant/nsapev2/header.h
new file mode 100644
index 00000000..462722e1
--- /dev/null
+++ b/Src/replicant/nsapev2/header.h
@@ -0,0 +1,39 @@
+#pragma once
+#include "foundation/types.h"
+#include "nu/ByteWriter.h"
+class nstest;
+
+namespace APEv2
+{
+ class Header
+ {
+ friend class ::nstest;
+ public:
+ Header();
+ Header(const void *data);
+ void SetSize(uint32_t size) { this->size = size; }
+ void SetFlags(uint32_t flags) { this->flags = flags; }
+ void SetItems(uint32_t items) { this->items = items; }
+ bool Valid() const;
+ uint32_t TagSize() const;
+ bool HasHeader() const;
+ bool HasFooter() const;
+ bool IsFooter() const;
+ bool IsHeader() const;
+ int Encode(bytewriter_t byte_writer) const;
+ uint32_t GetFlags() const;
+ enum
+ {
+ SIZE=32,
+ };
+ private:
+ uint8_t preamble[8];
+ uint32_t version;
+ uint32_t size;
+ uint32_t items;
+ uint32_t flags;
+ uint64_t reserved;
+ };
+}
+
+
diff --git a/Src/replicant/nsapev2/item.cpp b/Src/replicant/nsapev2/item.cpp
new file mode 100644
index 00000000..2d5184ea
--- /dev/null
+++ b/Src/replicant/nsapev2/item.cpp
@@ -0,0 +1,235 @@
+#include "item.h"
+#include "flags.h"
+#include "util.h"
+#include "nu/ByteWriter.h"
+#include "nu/strsafe.h"
+#include "nu/ByteReader.h"
+#include "nsapev2/nsapev2.h"
+#include <stdlib.h>
+
+/*
+http://wiki.hydrogenaudio.org/index.php?title=APE_Tag_Item
+
+Item layout:
+[0-3] length of value field (little endian)
+[4-7] flags (little endian)
+[null terminated] key
+[length] value
+*/
+
+APEv2::Item::Item()
+{
+ len=0;
+ flags=0;
+ key=0;
+ value=0;
+}
+
+APEv2::Item::~Item()
+{
+ free(key);
+ free(value);
+}
+
+int APEv2::Item::Read(bytereader_t byte_reader)
+{
+ if (bytereader_size(byte_reader) < 8)
+ return NErr_NeedMoreData;
+
+ /* read fixed-size fields */
+ len = bytereader_read_u32_le(byte_reader);
+ flags = bytereader_read_u32_le(byte_reader);
+
+ /* find the null terminator */
+ size_t key_len = bytereader_find_zero(byte_reader);
+
+ /* make sure we didn't hit the end of our buffer */
+ if (key_len == bytereader_size(byte_reader))
+ return NErr_Insufficient;
+
+ /* check for empty key and also check for integer overflow */
+ if (key_len == 0 || key_len+1 == 0)
+ return NErr_Error;
+
+ key = (char *)malloc(key_len+1);
+ if (key)
+ {
+ bytereader_read_n(byte_reader, key, key_len+1); /* read key and terminator*/
+
+ if (bytereader_size(byte_reader) < len) /* make sure we have room for the value! */
+ {
+ free(key);
+ key=0;
+ return NErr_NeedMoreData;
+ }
+
+ value = (char *)malloc(len);
+ if (value)
+ {
+ bytereader_read_n(byte_reader, value, len); /* read value */
+ return NErr_Success;
+ }
+ else
+ {
+ free(key);
+ key=0;
+ return NErr_OutOfMemory;
+ }
+ }
+ else
+ return NErr_OutOfMemory;
+
+}
+
+bool APEv2::Item::IsReadOnly()
+{
+ return flags & FLAG_READONLY;
+}
+
+bool APEv2::Item::KeyMatch(const char *key_to_compare, int compare)
+{
+ if (!key || !*key)
+ return false;
+
+ switch (compare)
+ {
+ case ITEM_KEY_COMPARE_CASE_INSENSITIVE:
+#ifdef _WIN32
+ return !_stricmp(key_to_compare, key);
+#else
+ return !strcasecmp(key_to_compare, key);
+#endif
+ case ITEM_KEY_COMPARE_CASE_SENSITIVE:
+ return !strcmp(key_to_compare, key);
+ default:
+ return false;
+ }
+}
+
+int APEv2::Item::Get(const void **data, size_t *datalen) const
+{
+ if (!value || !len)
+ return NErr_Empty;
+ *data = value;
+ *datalen = len;
+ return NErr_Success;
+}
+
+int APEv2::Item::Set(nx_string_t string)
+{
+ if (!value)
+ return NErr_BadParameter;
+
+ flags &= ~nsapev2_item_type_mask;
+ flags |= nsapev2_item_type_utf8;
+
+ size_t bytes;
+ int ret = NXStringGetBytesSize(&bytes, string, nx_charset_utf8, 0);
+ if (ret != NErr_DirectPointer && ret != NErr_Success)
+ return ret;
+
+ void *new_value = malloc(bytes);
+ if (!new_value)
+ return NErr_OutOfMemory;
+
+ size_t bytes_copied;
+ ret = NXStringGetBytes(&bytes_copied, string, new_value, bytes, nx_charset_utf8, 0);
+ if (ret != NErr_Success)
+ {
+ free(new_value);
+ return ret;
+ }
+
+ free(value);
+ value=new_value;
+ len=(uint32_t)bytes_copied;
+ return NErr_Success;
+}
+
+int APEv2::Item::Set(const void *data, size_t datalen, int data_type)
+{
+ if (!data || !datalen)
+ return NErr_Error;
+
+ // set data type for this item
+ flags &= ~nsapev2_item_type_mask;
+ flags |= data_type;
+
+ void *new_value = realloc(value, datalen);
+ if (!new_value)
+ return NErr_OutOfMemory;
+
+ value=new_value;
+ len=(uint32_t)datalen;
+ memcpy(value, data, len);
+ return NErr_Success;
+}
+
+ int APEv2::Item::New(size_t datalen, int data_type, void **bytes)
+ {
+ if (!datalen)
+ return NErr_Error;
+
+ // set data type for this item
+ flags &= ~nsapev2_item_type_mask;
+ flags |= data_type;
+
+ void *new_value = realloc(value, datalen);
+ if (!new_value)
+ return NErr_OutOfMemory;
+
+ value=new_value;
+
+ len=(uint32_t)datalen;
+ *bytes = value;
+ return NErr_Success;
+ }
+
+int APEv2::Item::SetKey(const char *tag)
+{
+ if (!tag || !*tag)
+ return NErr_Error;
+
+ char *new_key = strdup(tag);
+ if (!new_key)
+ return NErr_OutOfMemory;
+
+ free(key);
+ key = new_key;
+ return NErr_Success;
+}
+
+int APEv2::Item::GetKey(const char **tag) const
+{
+ if (!key)
+ return NErr_Error;
+ *tag = key;
+ return NErr_Success;
+}
+
+size_t APEv2::Item::EncodeSize() const
+{
+ return 4 /* size */ + 4 /* flags */ + strlen(key) + 1 /* NULL separator */ + len;
+}
+
+uint32_t APEv2::Item::GetFlags() const
+{
+ return flags;
+}
+
+int APEv2::Item::Encode(bytewriter_t byte_writer) const
+{
+ if (!key || !value || !len)
+ return NErr_Error;
+
+ if (bytewriter_size(byte_writer) < EncodeSize())
+ return NErr_Insufficient;
+
+ bytewriter_write_u32_le(byte_writer, len);
+ bytewriter_write_u32_le(byte_writer, flags);
+ bytewriter_write_n(byte_writer, key, strlen(key) + 1);
+ bytewriter_write_n(byte_writer, value, len);
+
+ return NErr_Success;
+}
+
diff --git a/Src/replicant/nsapev2/item.h b/Src/replicant/nsapev2/item.h
new file mode 100644
index 00000000..de07d472
--- /dev/null
+++ b/Src/replicant/nsapev2/item.h
@@ -0,0 +1,43 @@
+#pragma once
+#include "foundation/types.h"
+#include "nx/nxstring.h"
+#include "nu/PtrDeque.h"
+#include "nu/ByteWriter.h"
+#include "nu/ByteReader.h"
+namespace APEv2
+{
+ enum
+ {
+ ITEM_KEY_COMPARE_CASE_INSENSITIVE = 0,
+ ITEM_KEY_COMPARE_CASE_SENSITIVE = 1,
+ };
+
+ class Item : public nu::PtrDequeNode
+ {
+ public:
+ Item();
+ ~Item();
+
+ /* If successful, puts incremented data pointer in new_data, and new data size remaining in new_len */
+ int Read(bytereader_t byte_reader);
+
+ int Encode(bytewriter_t byte_writer) const;
+ size_t EncodeSize() const;
+
+ bool IsReadOnly();
+ bool KeyMatch(const char *key, int compare=ITEM_KEY_COMPARE_CASE_INSENSITIVE);
+ int Get(const void **data, size_t *len) const;
+ int GetKey(const char **tag) const;
+ int Set(nx_string_t value);
+ int Set(const void *data, size_t len, int dataType);
+ int SetKey(const char *tag);
+ int New(size_t datalen, int data_type, void **bytes);
+ uint32_t GetFlags() const;
+
+ private:
+ uint32_t flags;
+ char *key;
+ void *value;
+ uint32_t len;
+ };
+}
diff --git a/Src/replicant/nsapev2/nsapev2.h b/Src/replicant/nsapev2/nsapev2.h
new file mode 100644
index 00000000..1b703d1e
--- /dev/null
+++ b/Src/replicant/nsapev2/nsapev2.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include "foundation/types.h"
+#include "nx/nxstring.h"
+
+#ifdef __ANDROID__
+#include "android/nsapev2.h"
+#elif defined(_WIN32)
+#include "windows/nsapev2.h"
+#elif defined(__linux__)
+#include "linux/nsapev2.h"
+#elif defined (__APPLE__)
+#include "osx/nsapev2.h"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ typedef struct nsapev2_header_struct_t { } *nsapev2_header_t;
+ typedef struct nsapev2_tag_struct_t { } *nsapev2_tag_t;
+ typedef struct nsapev2_item_struct_t { } *nsapev2_item_t;
+
+ // must be exactly 32 bytes
+ NSAPEV2_EXPORT int NSAPEv2_Header_Valid(const void *header_data);
+ NSAPEV2_EXPORT int NSAPEv2_Header_Create(nsapev2_header_t *header, const void *header_data, size_t header_len);
+ NSAPEV2_EXPORT int NSAPEv2_Header_TagSize(const nsapev2_header_t header, uint32_t *tag_size);
+ NSAPEV2_EXPORT int NSAPEv2_Header_HasHeader(nsapev2_header_t header);
+ NSAPEV2_EXPORT int NSAPEv2_Header_Destroy(nsapev2_header_t header);
+
+
+ // currently, this function makes a copy of any necessary data. in the future, it would be better
+ // to make another version of this function that "borrows" your data
+ // if you can guarantee that the memory will outlive the nsapev2_tag_t object
+ NSAPEV2_EXPORT int NSAPEv2_Tag_Create(nsapev2_tag_t *tag, const nsapev2_header_t header, const void *bytes, size_t bytes_len);
+ NSAPEV2_EXPORT int NSAPEv2_Tag_New(nsapev2_tag_t *tag);
+ NSAPEV2_EXPORT int NSAPEv2_Tag_SerializedSize(const nsapev2_tag_t tag, size_t *bytes);
+ NSAPEV2_EXPORT int NSAPEv2_Tag_Serialize(const nsapev2_tag_t t, void *data, size_t bytes);
+ NSAPEV2_EXPORT int NSAPEv2_Tag_Destroy(nsapev2_tag_t tag);
+
+ NSAPEV2_EXPORT int NSAPEv2_Tag_GetFlags(nsapev2_tag_t tag, uint32_t *flags);
+ NSAPEV2_EXPORT int NSAPEv2_Tag_SetFlags(nsapev2_tag_t tag, uint32_t flags, uint32_t mask);
+
+ NSAPEV2_EXPORT int NSAPEv2_Tag_GetString(const nsapev2_tag_t tag, const char *key, unsigned int index, nx_string_t *value);
+ NSAPEV2_EXPORT int NSAPEv2_Tag_SetString(nsapev2_tag_t tag, const char *key, unsigned int index, nx_string_t value);
+
+ NSAPEV2_EXPORT int NSAPEv2_Tag_GetBinary(const nsapev2_tag_t tag, const char *key, unsigned int index, const void **bytes, size_t *length);
+ NSAPEV2_EXPORT int NSAPEv2_Tag_SetBinary(nsapev2_tag_t tag, const char *key, unsigned int index, const void *bytes, size_t length);
+ NSAPEV2_EXPORT int NSAPEv2_Tag_SetArtwork(nsapev2_tag_t t, const char *key, unsigned int index, const char *filename, const void *bytes, size_t length);
+ /* Items */
+ // flags
+ static const uint32_t nsapev2_item_read_write = 0;
+ static const uint32_t nsapev2_item_read_only = 1;
+
+ static const uint32_t nsapev2_item_type_utf8 = 0;
+ static const uint32_t nsapev2_item_type_binary = 1 << 1;
+ static const uint32_t nsapev2_item_type_locator = 2 << 1;
+
+ // mask flags with these to check the different fields
+ static const uint32_t nsapev2_item_type_mask = (1 << 2) | (1 << 1);
+ static const uint32_t nsapev2_item_read_mask = 1 << 0;
+
+ NSAPEV2_EXPORT int NSAPEv2_Tag_EnumerateItems(const nsapev2_tag_t tag, const nsapev2_item_t start, nsapev2_item_t *next);
+ NSAPEV2_EXPORT int NSAPEv2_Tag_GetItemCount(const nsapev2_tag_t t, size_t *count);
+ NSAPEV2_EXPORT int NSAPEv2_Tag_GetItemAtIndex(const nsapev2_tag_t t, unsigned int index, nsapev2_item_t *item);
+ NSAPEV2_EXPORT int NSAPEv2_Item_GetInformation(const nsapev2_item_t item, const char **key, uint32_t *flags);
+ NSAPEV2_EXPORT int NSAPEv2_Item_GetString(const nsapev2_item_t item, nx_string_t *value);
+ NSAPEV2_EXPORT int NSAPEv2_Item_GetBinary(const nsapev2_item_t item, const void **bytes, size_t *length);
+ NSAPEV2_EXPORT int NSAPEv2_Item_SetKey(nsapev2_item_t item, const char *key);
+ NSAPEV2_EXPORT int NSAPEv2_Item_SetString(nsapev2_item_t item, nx_string_t value);
+ NSAPEV2_EXPORT int NSAPEv2_Tag_RemoveItem(nsapev2_tag_t tag, nsapev2_item_t item);
+ NSAPEV2_EXPORT int NSAPEv2_Tag_AddItem(nsapev2_tag_t tag, nsapev2_item_t *item);
+#ifdef __cplusplus
+}
+#endif
diff --git a/Src/replicant/nsapev2/nsapev2.sln b/Src/replicant/nsapev2/nsapev2.sln
new file mode 100644
index 00000000..10f0a861
--- /dev/null
+++ b/Src/replicant/nsapev2/nsapev2.sln
@@ -0,0 +1,81 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsapev2", "nsapev2.vcxproj", "{BF92BB16-012A-43F4-BC67-30D6D5187598}"
+ ProjectSection(ProjectDependencies) = postProject
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27} = {57C90706-B25D-4ACA-9B33-95CDB2427C27}
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}
+ {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D}
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\zlib\zlib.vcxproj", "{0F9730E4-45DA-4BD2-A50A-403A4BC9751A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Debug|x64 = Debug|x64
+ Release|Win32 = Release|Win32
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {BF92BB16-012A-43F4-BC67-30D6D5187598}.Debug|Win32.ActiveCfg = Debug|Win32
+ {BF92BB16-012A-43F4-BC67-30D6D5187598}.Debug|Win32.Build.0 = Debug|Win32
+ {BF92BB16-012A-43F4-BC67-30D6D5187598}.Debug|x64.ActiveCfg = Debug|x64
+ {BF92BB16-012A-43F4-BC67-30D6D5187598}.Debug|x64.Build.0 = Debug|x64
+ {BF92BB16-012A-43F4-BC67-30D6D5187598}.Release|Win32.ActiveCfg = Release|Win32
+ {BF92BB16-012A-43F4-BC67-30D6D5187598}.Release|Win32.Build.0 = Release|Win32
+ {BF92BB16-012A-43F4-BC67-30D6D5187598}.Release|x64.ActiveCfg = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.Build.0 = Debug|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.ActiveCfg = Debug|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.Build.0 = Debug|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.ActiveCfg = Release|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.Build.0 = Release|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.ActiveCfg = Release|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C4E799C1-027B-487B-8E1A-31F2D26A1AFE}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/replicant/nsapev2/nsapev2.vcxproj b/Src/replicant/nsapev2/nsapev2.vcxproj
new file mode 100644
index 00000000..cf514039
--- /dev/null
+++ b/Src/replicant/nsapev2/nsapev2.vcxproj
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{BF92BB16-012A-43F4-BC67-30D6D5187598}</ProjectGuid>
+ <RootNamespace>nsapev2</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>x86_Debug\</OutDir>
+ <IntDir>x86_Debug\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>x64_Debug\</OutDir>
+ <IntDir>x64_Debug\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>x86_Release\</OutDir>
+ <IntDir>x86_Release\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>x64_Release\</OutDir>
+ <IntDir>x64_Release\</IntDir>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <Lib>
+ <OutputFile>$(ProjectDir)x86_Debug\$(ProjectName).lib</OutputFile>
+ </Lib>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <Lib>
+ <OutputFile>$(ProjectDir)x64_Debug\$(ProjectName).lib</OutputFile>
+ </Lib>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <Lib>
+ <OutputFile>$(ProjectDir)x86_Release\$(ProjectName).lib</OutputFile>
+ </Lib>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <Lib>
+ <OutputFile>$(ProjectDir)x64_Release\$(ProjectName).lib</OutputFile>
+ </Lib>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="header.cpp" />
+ <ClCompile Include="item.cpp" />
+ <ClCompile Include="nsapev2_common.cpp" />
+ <ClCompile Include="tag.cpp" />
+ <ClCompile Include="windows\nsapev2.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="flags.h" />
+ <ClInclude Include="header.h" />
+ <ClInclude Include="item.h" />
+ <ClInclude Include="nsapev2.h" />
+ <ClInclude Include="precomp.h" />
+ <ClInclude Include="tag.h" />
+ <ClInclude Include="util.h" />
+ <ClInclude Include="windows\nsapev2.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/Src/replicant/nsapev2/nsapev2_common.cpp b/Src/replicant/nsapev2/nsapev2_common.cpp
new file mode 100644
index 00000000..badcb100
--- /dev/null
+++ b/Src/replicant/nsapev2/nsapev2_common.cpp
@@ -0,0 +1,465 @@
+#include "nsapev2.h"
+#include "nsapev2/header.h"
+#include "nsapev2/tag.h"
+#include <new>
+
+int NSAPEv2_Header_Valid(const void *header_data)
+
+{
+ if (APEv2::Header(header_data).Valid())
+ return NErr_Success;
+ else
+ return NErr_False;
+}
+
+int NSAPEv2_Header_Create(nsapev2_header_t *header, const void *header_data, size_t header_len)
+{
+ if (header_len < 10 || NSAPEv2_Header_Valid(header_data) != NErr_Success)
+ return NErr_Error;
+
+ nsapev2_header_t new_header = (nsapev2_header_t)new (std::nothrow) APEv2::Header(header_data);
+ if (!new_header)
+ return NErr_OutOfMemory;
+ *header = new_header;
+ return NErr_Success;
+}
+
+int NSAPEv2_Header_TagSize(const nsapev2_header_t h, uint32_t *tag_size)
+{
+ const APEv2::Header *header = (const APEv2::Header *)h;
+ if (!header)
+ return NErr_NullPointer;
+
+ *tag_size = header->TagSize();
+ return NErr_Success;
+}
+
+int NSAPEv2_Header_HasHeader(nsapev2_header_t h)
+{
+ const APEv2::Header *header = (const APEv2::Header *)h;
+ if (!header)
+ return NErr_NullPointer;
+
+ if (header->HasHeader())
+ return NErr_Success;
+ else
+ return NErr_False;
+}
+
+int NSAPEv2_Header_Destroy(nsapev2_header_t h)
+{
+ const APEv2::Header *header = (const APEv2::Header *)h;
+ if (!header)
+ return NErr_NullPointer;
+
+ delete header;
+ return NErr_Success;
+}
+
+int NSAPEv2_Tag_Create(nsapev2_tag_t *t, const nsapev2_header_t h, const void *bytes, size_t bytes_len)
+{
+ const APEv2::Header *header = (const APEv2::Header *)h;
+ if (!header)
+ return NErr_NullPointer;
+
+ APEv2::Tag *tag = new (std::nothrow) APEv2::Tag;
+ if (!tag)
+ return NErr_OutOfMemory;
+
+ int ret = tag->Parse(header, bytes, bytes_len);
+ if (ret != NErr_Success)
+ {
+ delete tag;
+ return ret;
+ }
+
+ *t = (nsapev2_tag_t)tag;
+ return ret;
+}
+
+int NSAPEv2_Tag_New(nsapev2_tag_t *t)
+{
+ APEv2::Header *new_header = new (std::nothrow) APEv2::Header();
+ if (!new_header)
+ return NErr_OutOfMemory;
+
+ APEv2::Tag *tag = new (std::nothrow) APEv2::Tag;
+ if (!tag)
+ {
+ delete new_header;
+ return NErr_OutOfMemory;
+ }
+
+ *t = (nsapev2_tag_t)tag;
+ return NErr_Success;
+}
+
+int NSAPEv2_Tag_SerializedSize(const nsapev2_tag_t t, size_t *bytes)
+{
+ const APEv2::Tag *tag = (const APEv2::Tag *)t;
+ if (!tag)
+ return NErr_BadParameter;
+
+ *bytes = tag->EncodeSize();
+ return NErr_Success;
+}
+
+int NSAPEv2_Tag_Serialize(const nsapev2_tag_t t, void *data, size_t bytes)
+{
+ const APEv2::Tag *tag = (const APEv2::Tag *)t;
+ if (!tag)
+ return NErr_BadParameter;
+
+ return tag->Encode(data, bytes);
+}
+
+int NSAPEv2_Tag_Destroy(nsapev2_tag_t t)
+{
+ APEv2::Tag *tag = (APEv2::Tag *)t;
+ if (!tag)
+ return NErr_NullPointer;
+ delete tag;
+ return NErr_Success;
+}
+
+int NSAPEv2_Tag_GetFlags(nsapev2_tag_t t, uint32_t *flags)
+{
+ APEv2::Tag *tag = (APEv2::Tag *)t;
+ if (!tag)
+ return NErr_BadParameter;
+
+ return tag->GetFlags(flags);
+}
+
+int NSAPEv2_Tag_SetFlags(nsapev2_tag_t t, uint32_t flags, uint32_t mask)
+{
+ APEv2::Tag *tag = (APEv2::Tag *)t;
+ if (!tag)
+ return NErr_BadParameter;
+
+ return tag->SetFlags(flags, mask);
+}
+
+int NSAPEv2_Tag_GetString(const nsapev2_tag_t t, const char *key, unsigned int index, nx_string_t *value)
+{
+ const APEv2::Tag *tag = (const APEv2::Tag *)t;
+ if (!tag)
+ return NErr_Empty;
+
+ const void *data;
+ size_t data_len;
+ int ret = tag->GetData(key, index, &data, &data_len);
+ if (ret != NErr_Success)
+ return ret;
+
+ return NXStringCreateWithBytes(value, data, data_len, nx_charset_utf8);
+}
+
+int NSAPEv2_Tag_SetString(nsapev2_tag_t t, const char *key, unsigned int index, nx_string_t value)
+{
+ APEv2::Tag *tag = (APEv2::Tag *)t;
+ if (!tag)
+ return NErr_BadParameter;
+
+ if (value)
+ {
+ APEv2::Item *item;
+ int ret = tag->GetItem(key, index, &item);
+ if (ret == NErr_Success)
+ {
+ return item->Set(value);
+ }
+ else
+ {
+ /* no such item, need to make a new one */
+ item = new (std::nothrow) APEv2::Item;
+ if (!item)
+ return NErr_OutOfMemory;
+
+ ret = item->SetKey(key);
+ if (ret != NErr_Success)
+ {
+ delete item;
+ return ret;
+ }
+
+ ret = item->Set(value);
+ if (ret != NErr_Success)
+ {
+ delete item;
+ return ret;
+ }
+
+ ret = tag->AddItem(item);
+ if (ret != NErr_Success)
+ {
+ delete item;
+ return ret;
+ }
+
+ return NErr_Success;
+ }
+ }
+ else
+ {
+ tag->Remove(key, index);
+ return NErr_Success;
+ }
+}
+
+int NSAPEv2_Tag_GetBinary(const nsapev2_tag_t t, const char *key, unsigned int index, const void **bytes, size_t *length)
+{
+ const APEv2::Tag *tag = (const APEv2::Tag *)t;
+ if (!tag)
+ return NErr_Empty;
+
+ return tag->GetData(key, index, bytes, length);
+}
+
+int NSAPEv2_Tag_SetBinary(nsapev2_tag_t t, const char *key, unsigned int index, const void *bytes, size_t length)
+{
+ APEv2::Tag *tag = (APEv2::Tag *)t;
+ if (!tag)
+ return NErr_BadParameter;
+
+ if (bytes)
+ {
+ APEv2::Item *item;
+ int ret = tag->GetItem(key, index, &item);
+ if (ret == NErr_Success)
+ {
+ return item->Set(bytes, length, nsapev2_item_type_binary);
+ }
+ else
+ {
+ /* no such item, need to make a new one */
+ item = new (std::nothrow) APEv2::Item;
+ if (!item)
+ return NErr_OutOfMemory;
+
+ ret = item->SetKey(key);
+ if (ret != NErr_Success)
+ {
+ delete item;
+ return ret;
+ }
+
+ ret = item->Set(bytes, length, nsapev2_item_type_binary);
+ if (ret != NErr_Success)
+ {
+ delete item;
+ return ret;
+ }
+
+ ret = tag->AddItem(item);
+ if (ret != NErr_Success)
+ {
+ delete item;
+ return ret;
+ }
+
+ return NErr_Success;
+ }
+ }
+ else
+ {
+ tag->Remove(key, index);
+ return NErr_Success;
+ }
+}
+
+static int SetArtwork(APEv2::Item *item, const char *filename, const void *bytes, size_t length)
+{
+
+ size_t filename_length = filename?strlen(filename):0;
+ size_t total_length = filename_length
+ + 1 /* null separator */
+ + length;
+
+ void *item_data;
+ int ret = item->New(total_length, nsapev2_item_type_binary, &item_data);
+ if (ret != NErr_Success)
+ return ret;
+
+ bytewriter_s byte_writer;
+ bytewriter_init(&byte_writer, item_data, total_length);
+ bytewriter_write_n(&byte_writer, filename, filename_length);
+ bytewriter_write_u8(&byte_writer, 0);
+ bytewriter_write_n(&byte_writer, bytes, length);
+
+ return NErr_Success;
+}
+
+int NSAPEv2_Tag_SetArtwork(nsapev2_tag_t t, const char *key, unsigned int index, const char *filename, const void *bytes, size_t length)
+{
+ APEv2::Tag *tag = (APEv2::Tag *)t;
+ if (!tag)
+ return NErr_BadParameter;
+
+ if (bytes)
+ {
+
+ APEv2::Item *item;
+ int ret = tag->GetItem(key, index, &item);
+ if (ret == NErr_Success)
+ {
+ return SetArtwork(item, filename, bytes, length);
+ }
+ else
+ {
+ /* no such item, need to make a new one */
+ item = new (std::nothrow) APEv2::Item;
+ if (!item)
+ return NErr_OutOfMemory;
+
+ ret = item->SetKey(key);
+ if (ret != NErr_Success)
+ {
+ delete item;
+ return ret;
+ }
+
+ ret = SetArtwork(item, filename, bytes, length);
+ if (ret != NErr_Success)
+ {
+ delete item;
+ return ret;
+ }
+
+ ret = tag->AddItem(item);
+ if (ret != NErr_Success)
+ {
+ delete item;
+ return ret;
+ }
+
+ return NErr_Success;
+ }
+ }
+ else
+ {
+ tag->Remove(key, index);
+ return NErr_Success;
+ }
+}
+
+int NSAPEv2_Tag_EnumerateItems(const nsapev2_tag_t t, const nsapev2_item_t s, nsapev2_item_t *next)
+{
+ const APEv2::Tag *tag = (const APEv2::Tag *)t;
+ if (!tag)
+ return NErr_NullPointer;
+
+ const APEv2::Item *start = (const APEv2::Item *)s;
+ return tag->EnumerateItems(start, (APEv2::Item **)next);
+}
+
+int NSAPEv2_Tag_GetItemCount(const nsapev2_tag_t t, size_t *count)
+{
+ const APEv2::Tag *tag = (const APEv2::Tag *)t;
+ if (!tag)
+ return NErr_NullPointer;
+
+ return tag->GetItemCount(count);
+}
+
+
+int NSAPEv2_Tag_GetItemAtIndex(const nsapev2_tag_t t, unsigned int index, nsapev2_item_t *item)
+{
+ APEv2::Tag *tag = (APEv2::Tag *)t;
+ if (!tag)
+ return NErr_NullPointer;
+
+ return tag->GetItemAtIndex(index, (APEv2::Item **)item);
+}
+
+int NSAPEv2_Item_GetInformation(const nsapev2_item_t i, const char **key, uint32_t *flags)
+{
+ const APEv2::Item *item = (const APEv2::Item *)i;
+ if (!item)
+ return NErr_NullPointer;
+
+ if (key)
+ {
+ int ret = item->GetKey(key);
+ if (ret != NErr_Success)
+ return ret;
+ }
+
+ if (flags)
+ *flags = item->GetFlags();
+ return NErr_Success;
+
+}
+
+int NSAPEv2_Item_GetString(const nsapev2_item_t i, nx_string_t *value)
+{
+ const APEv2::Item *item = (const APEv2::Item *)i;
+ if (!item)
+ return NErr_BadParameter;
+
+ const void *data;
+ size_t data_len;
+ int ret = item->Get(&data, &data_len);
+ if (ret != NErr_Success)
+ return ret;
+
+ return NXStringCreateWithBytes(value, data, data_len, nx_charset_utf8);
+}
+
+int NSAPEv2_Item_GetBinary(const nsapev2_item_t i, const void **bytes, size_t *length)
+{
+ const APEv2::Item *item = (const APEv2::Item *)i;
+ if (!item)
+ return NErr_BadParameter;
+
+ return item->Get(bytes, length);
+}
+
+int NSAPEv2_Item_SetKey(nsapev2_item_t i, const char *key)
+{
+ APEv2::Item *item = (APEv2::Item *)i;
+ if (!item)
+ return NErr_BadParameter;
+
+ return item->SetKey(key);
+}
+
+int NSAPEv2_Item_SetString(nsapev2_item_t i, nx_string_t value)
+{
+ APEv2::Item *item = (APEv2::Item *)i;
+ if (!item)
+ return NErr_BadParameter;
+
+ return item->Set(value);
+}
+
+int NSAPEv2_Tag_RemoveItem(nsapev2_tag_t t, nsapev2_item_t i)
+{
+ APEv2::Tag *tag = (APEv2::Tag *)t;
+ if (!tag)
+ return NErr_BadParameter;
+
+ APEv2::Item *item = (APEv2::Item *)i;
+ if (!item)
+ return NErr_BadParameter;
+
+ tag->RemoveItem(item);
+ return NErr_Success;
+}
+
+int NSAPEv2_Tag_AddItem(nsapev2_tag_t t, nsapev2_item_t *i)
+{
+ APEv2::Tag *tag = (APEv2::Tag *)t;
+ if (!tag)
+ return NErr_BadParameter;
+
+ APEv2::Item **item = (APEv2::Item **)i;
+ if (!item)
+ return NErr_NullPointer;
+
+ *item = new (std::nothrow) APEv2::Item;
+ if (!*item)
+ return NErr_OutOfMemory;
+
+ return tag->AddItem(*item);
+} \ No newline at end of file
diff --git a/Src/replicant/nsapev2/precomp.h b/Src/replicant/nsapev2/precomp.h
new file mode 100644
index 00000000..5d7ef44d
--- /dev/null
+++ b/Src/replicant/nsapev2/precomp.h
@@ -0,0 +1,26 @@
+//
+// precomp.h
+// nsapev2
+//
+
+#include "foundation/error.h"
+#include "foundation/types.h"
+
+#include "nu/ByteReader.h"
+#include "nu/ByteWriter.h"
+#include "nu/strsafe.h"
+
+#ifdef __cplusplus
+#include "nu/PtrDeque.h"
+#endif
+
+#include "nx/nxstring.h"
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __cplusplus
+#include "header.h"
+#include "new"
+#endif
diff --git a/Src/replicant/nsapev2/tag.cpp b/Src/replicant/nsapev2/tag.cpp
new file mode 100644
index 00000000..fcaeb091
--- /dev/null
+++ b/Src/replicant/nsapev2/tag.cpp
@@ -0,0 +1,285 @@
+#include "tag.h"
+#include "header.h"
+#include "flags.h"
+#ifdef _WIN32
+#include "../nu/ns_wc.h"
+#include "nu/strsafe.h"
+#endif
+#include <limits.h>
+
+#include <new>
+#include "nu/ByteWriter.h"
+
+/*
+http://wiki.hydrogenaudio.org/index.php?title=APEv2_specification
+*/
+
+APEv2::Tag::Tag()
+{
+ flags = 0; // default to writing just a footer
+}
+
+APEv2::Tag::~Tag()
+{
+ items.deleteAll();
+}
+
+/* Parsing */
+int APEv2::Tag::Parse(const APEv2::Header *header, const void *_data, size_t len)
+{
+ flags = header->GetFlags();
+
+ if (header->IsFooter())
+ {
+ flags &= ~FLAG_HEADER_NO_FOOTER; // winamp 5.54 had this flag reversed, so let's correct it
+ if (header->HasHeader())
+ {
+ // TODO: validate header
+ _data = (const uint8_t *)_data + 32;
+ len -= 32;
+ }
+ len -= 32; /* footer is counted in the size */
+ return ParseData(_data, len);
+ }
+ else /* IsHeader() */
+ {
+ if (header->HasFooter())
+ {
+ // TODO: validate footer
+ len -= 32;
+ }
+ return ParseData(_data, len);
+ }
+}
+
+int APEv2::Tag::ParseData(const void *data, size_t len)
+{
+ bytereader_s byte_reader;
+ bytereader_init(&byte_reader, data, len);
+
+ while (bytereader_size(&byte_reader))
+ {
+ Item *item = new (std::nothrow) Item;
+ if (!item)
+ return NErr_OutOfMemory;
+
+ int ret = item->Read(&byte_reader);
+ if (ret == NErr_Success)
+ {
+ items.push_back(item);
+ }
+ else
+ {
+ delete item;
+ return ret;
+ }
+ }
+ return NErr_Success;
+}
+
+/* Retrieving Data */
+int APEv2::Tag::GetItem(const char *key, unsigned int index, Item **item, int compare) const
+{
+ unsigned int i=0;
+ for (ItemList::iterator itr=items.begin();itr!=items.end();itr++)
+ {
+ /* check if it's a string first, and then match the key next (will be faster) */
+ if (itr->KeyMatch(key, compare))
+ {
+ if (i++ < index)
+ continue;
+
+ *item = *itr;
+ return NErr_Success;
+ }
+ }
+
+ if (i > index) /* if we found the key once, but the index was too high */
+ return NErr_EndOfEnumeration;
+ return NErr_Empty;
+}
+
+int APEv2::Tag::GetItemAtIndex(unsigned int index, Item **item) const
+{
+ unsigned int i=0;
+ for (ItemList::iterator itr=items.begin();itr!=items.end();itr++)
+ {
+ if (i++ < index)
+ continue;
+
+ *item = *itr;
+ return NErr_Success;
+ }
+
+ return NErr_EndOfEnumeration;
+}
+
+int APEv2::Tag::GetData(const char *key, unsigned int index, const void **data, size_t *data_len, int compare) const
+{
+ Item *item=0;
+ int ret = GetItem(key, index, &item, compare);
+ if (ret != NErr_Success)
+ return ret;
+
+ return item->Get(data, data_len);
+}
+
+int APEv2::Tag::EnumerateItems(const Item *start, Item **item) const
+{
+ Item *next_item = 0;
+ if (!start)
+ {
+ next_item = items.front();
+ }
+ else
+ {
+ next_item = static_cast<APEv2::Item *>(start->next);
+ }
+ *item = next_item;
+ if (next_item)
+ return NErr_Success;
+ else if (start)
+ return NErr_EndOfEnumeration;
+ else
+ return NErr_Empty;
+}
+
+int APEv2::Tag::FindItemByKey(const char *key, Item **item, int compare) const
+{
+ for (ItemList::iterator itr=items.begin();itr!=items.end();itr++)
+ {
+ if (itr->KeyMatch(key, compare))
+ {
+ *item = *itr;
+ return NErr_Success;
+ }
+ }
+ return NErr_Unknown;
+}
+
+bool APEv2::Tag::IsReadOnly() const
+{
+ return flags & FLAG_READONLY;
+}
+
+int APEv2::Tag::GetItemCount(size_t *count) const
+{
+ *count = items.size();
+ return NErr_Success;
+}
+
+int APEv2::Tag::GetFlags(uint32_t *flags) const
+{
+ *flags = this->flags;
+ return NErr_Success;
+}
+
+/* Setting Data */
+int APEv2::Tag::AddItem(APEv2::Item *new_item)
+{
+ items.push_back(new_item);
+ return NErr_Success;
+}
+
+int APEv2::Tag::SetFlags(uint32_t newflags, uint32_t mask)
+{
+ flags = (flags & ~mask) | newflags;
+ return NErr_Success;
+}
+
+/* Removing Data */
+void APEv2::Tag::Clear()
+{
+ items.deleteAll();
+}
+
+void APEv2::Tag::Remove(const char *key, unsigned int starting_index, int compare)
+{
+ for (ItemList::iterator itr=items.begin();itr!=items.end();)
+ {
+ ItemList::iterator next = itr;
+ next++;
+ APEv2::Item *item = *itr;
+
+ if (item->KeyMatch(key, compare))
+ {
+ if (starting_index)
+ {
+ starting_index--;
+ }
+ else
+ {
+ items.erase(item);
+ delete item;
+ }
+ }
+ itr=next;
+ }
+}
+
+void APEv2::Tag::RemoveItem(Item *item)
+{
+ items.erase(item);
+ delete item;
+}
+
+/* Serializing */
+size_t APEv2::Tag::EncodeSize() const
+{
+ size_t total_size=0;
+
+ if (flags & FLAG_HEADER_HAS_HEADER)
+ total_size+=Header::SIZE;
+
+ for (ItemList::iterator itr=items.begin();itr!=items.end();itr++)
+ {
+ total_size += itr->EncodeSize();
+ }
+
+ if (!(flags & FLAG_HEADER_NO_FOOTER))
+ total_size+=Header::SIZE;
+
+ return total_size;
+}
+
+int APEv2::Tag::Encode(void *data, size_t len) const
+{
+ bytewriter_s byte_writer;
+ bytewriter_init(&byte_writer, data, len);
+
+ if (flags & FLAG_HEADER_HAS_HEADER)
+ {
+ Header header;
+ header.SetSize((uint32_t)len - Header::SIZE);
+ header.SetItems((uint32_t)items.size());
+ header.SetFlags((flags & FLAG_HEADER_ENCODE_MASK)|FLAG_HEADER_IS_HEADER);
+
+ int ret = header.Encode(&byte_writer);
+ if (ret != NErr_Success)
+ return ret;
+ }
+
+ for (ItemList::iterator itr=items.begin();itr!=items.end();itr++)
+ {
+ int ret = itr->Encode(&byte_writer);
+ if (ret!= NErr_Success)
+ return ret;
+ }
+
+ if (!(flags & FLAG_HEADER_NO_FOOTER))
+ {
+ Header footer;
+
+ if (flags & FLAG_HEADER_HAS_HEADER)
+ footer.SetSize((uint32_t)len - Header::SIZE);
+ else
+ footer.SetSize((uint32_t)len);
+ footer.SetItems((uint32_t)items.size());
+ footer.SetFlags((flags & FLAG_HEADER_ENCODE_MASK));
+
+ int ret = footer.Encode(&byte_writer);
+ if (ret != NErr_Success)
+ return ret;
+ }
+ return NErr_Success;
+}
diff --git a/Src/replicant/nsapev2/tag.h b/Src/replicant/nsapev2/tag.h
new file mode 100644
index 00000000..a43c0499
--- /dev/null
+++ b/Src/replicant/nsapev2/tag.h
@@ -0,0 +1,50 @@
+#pragma once
+#include "foundation/types.h"
+#include "nu/PtrDeque.h"
+#include "item.h"
+#include "flags.h"
+#include "header.h"
+
+namespace APEv2
+{
+class Tag
+{
+public:
+ Tag();
+ ~Tag();
+
+ /* Parsing */
+ int Parse(const APEv2::Header *header, const void *data, size_t len);
+
+ /* Retrieving Data */
+ int GetItem(const char *key, unsigned int index, Item **item, int compare=ITEM_KEY_COMPARE_CASE_INSENSITIVE) const;
+ int GetItemAtIndex(unsigned int index, Item **) const;
+ int GetData(const char *tag, unsigned int index, const void **data, size_t *data_len, int compare=ITEM_KEY_COMPARE_CASE_INSENSITIVE) const;
+ int EnumerateItems(const Item *start, Item **item) const;
+ int FindItemByKey(const char *key, Item **item, int compare=ITEM_KEY_COMPARE_CASE_INSENSITIVE) const;
+ bool IsReadOnly() const;
+ int GetItemCount(size_t *count) const;
+ int GetFlags(uint32_t *flags) const;
+
+ /* Setting Data */
+ int AddItem(APEv2::Item *new_item);
+ int SetFlags(uint32_t flags, uint32_t mask);
+
+ /* Removing Data */
+ void Clear();
+ void Remove(const char *key, unsigned int starting_index, int compare=ITEM_KEY_COMPARE_CASE_INSENSITIVE); // removes all items matching a key, but skips the first 'starting_index' items
+ void RemoveItem(Item *item);
+ /* Serializing */
+ size_t EncodeSize() const;
+ int Encode(void *data, size_t len) const;
+
+private: /* methods */
+ int ParseData(const void *data, size_t len); /* helper function, call with data pointing to beginning of items block (skip header), and length without footer. */
+
+private: /* data */
+ typedef nu::PtrDeque<Item> ItemList;
+ ItemList items;
+ uint32_t flags;
+};
+}
+
diff --git a/Src/replicant/nsapev2/util.h b/Src/replicant/nsapev2/util.h
new file mode 100644
index 00000000..6f70f09b
--- /dev/null
+++ b/Src/replicant/nsapev2/util.h
@@ -0,0 +1 @@
+#pragma once
diff --git a/Src/replicant/nsapev2/windows/nsapev2.cpp b/Src/replicant/nsapev2/windows/nsapev2.cpp
new file mode 100644
index 00000000..b8d4ee0c
--- /dev/null
+++ b/Src/replicant/nsapev2/windows/nsapev2.cpp
@@ -0,0 +1 @@
+#include "nsapev2/nsapev2.h"
diff --git a/Src/replicant/nsapev2/windows/nsapev2.h b/Src/replicant/nsapev2/windows/nsapev2.h
new file mode 100644
index 00000000..cdf0c17c
--- /dev/null
+++ b/Src/replicant/nsapev2/windows/nsapev2.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "foundation/export.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+#ifdef NSID3V2_EXPORTS
+#define NSAPEV2_EXPORT __declspec(dllexport)
+#else
+#define NSAPEV2_EXPORT __declspec(dllimport)
+#endif
+*/
+#define NSAPEV2_EXPORT
+
+
+#ifdef __cplusplus
+}
+#endif \ No newline at end of file