aboutsummaryrefslogtreecommitdiff
path: root/Src/apev2
diff options
context:
space:
mode:
Diffstat (limited to 'Src/apev2')
-rw-r--r--Src/apev2/apev2.sln30
-rw-r--r--Src/apev2/apev2.vcxproj195
-rw-r--r--Src/apev2/apev2.vcxproj.filters42
-rw-r--r--Src/apev2/flags.h40
-rw-r--r--Src/apev2/header.cpp88
-rw-r--r--Src/apev2/header.h44
-rw-r--r--Src/apev2/item.cpp221
-rw-r--r--Src/apev2/item.h43
-rw-r--r--Src/apev2/tag.cpp353
-rw-r--r--Src/apev2/tag.h55
-rw-r--r--Src/apev2/util.h22
11 files changed, 1133 insertions, 0 deletions
diff --git a/Src/apev2/apev2.sln b/Src/apev2/apev2.sln
new file mode 100644
index 00000000..605c2fe2
--- /dev/null
+++ b/Src/apev2/apev2.sln
@@ -0,0 +1,30 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29424.173
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "apev2", "apev2.vcxproj", "{48387E27-2666-4ACF-9C21-AE508C529580}"
+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
+ {48387E27-2666-4ACF-9C21-AE508C529580}.Debug|Win32.ActiveCfg = Debug|Win32
+ {48387E27-2666-4ACF-9C21-AE508C529580}.Debug|Win32.Build.0 = Debug|Win32
+ {48387E27-2666-4ACF-9C21-AE508C529580}.Release|Win32.ActiveCfg = Release|Win32
+ {48387E27-2666-4ACF-9C21-AE508C529580}.Release|Win32.Build.0 = Release|Win32
+ {48387E27-2666-4ACF-9C21-AE508C529580}.Debug|x64.ActiveCfg = Debug|x64
+ {48387E27-2666-4ACF-9C21-AE508C529580}.Debug|x64.Build.0 = Debug|x64
+ {48387E27-2666-4ACF-9C21-AE508C529580}.Release|x64.ActiveCfg = Release|x64
+ {48387E27-2666-4ACF-9C21-AE508C529580}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {5A83B542-4B24-4F95-B752-B5D7567B4F54}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/apev2/apev2.vcxproj b/Src/apev2/apev2.vcxproj
new file mode 100644
index 00000000..bf5da763
--- /dev/null
+++ b/Src/apev2/apev2.vcxproj
@@ -0,0 +1,195 @@
+<?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>{48387E27-2666-4ACF-9C21-AE508C529580}</ProjectGuid>
+ <RootNamespace>apev2</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+ </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>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg">
+ <VcpkgEnabled>false</VcpkgEnabled>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>../;../Wasabi;../replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Lib>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+ </Lib>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>../;../Wasabi;../replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Lib>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+ </Lib>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>../;../Wasabi;../replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <ForcedIncludeFiles>tag.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Lib>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+ </Lib>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>../;../Wasabi;../replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <ForcedIncludeFiles>tag.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Lib>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+ </Lib>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="flags.h" />
+ <ClInclude Include="header.h" />
+ <ClInclude Include="item.h" />
+ <ClInclude Include="tag.h" />
+ <ClInclude Include="util.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="header.cpp" />
+ <ClCompile Include="item.cpp" />
+ <ClCompile Include="tag.cpp" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/Src/apev2/apev2.vcxproj.filters b/Src/apev2/apev2.vcxproj.filters
new file mode 100644
index 00000000..e6996420
--- /dev/null
+++ b/Src/apev2/apev2.vcxproj.filters
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="header.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="item.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="tag.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="flags.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="header.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="item.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="tag.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="util.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{6cf90466-6c89-4be4-83c3-e7872a927708}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{eaaa2f1a-68ef-4456-8bfa-47b0c6183c31}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{6cd9a1b4-b2a9-412e-a920-a163ab8e3cf5}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/apev2/flags.h b/Src/apev2/flags.h
new file mode 100644
index 00000000..f0c2d6cb
--- /dev/null
+++ b/Src/apev2/flags.h
@@ -0,0 +1,40 @@
+#ifndef NULLSOFT_APEV2_FLAGS_H
+#define NULLSOFT_APEV2_FLAGS_H
+
+namespace APEv2
+{
+enum
+{
+ APEV2_SUCCESS = 0,
+ APEV2_FAILURE = 1,
+ APEV2_TOO_SMALL = 2,
+ APEV2_KEY_NOT_FOUND=3,
+ APEV2_NO_DATA = 4, /* Key found, but data is empty or corrupt */
+ APEV2_END_OF_ITEMS=5,
+};
+/*
+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,
+
+ /* item specific flags */
+ MASK_ITEM_TYPE = ((1 << 2) | (1 << 1)),
+
+ FLAG_ITEM_TEXT = 0,
+ FLAG_ITEM_BINARY = (1 << 1), /* We compare WITHOUT SHIFTING so all flag values are << 1 */
+ FLAG_ITEM_LOCATOR = (2 << 1),
+ FLAG_ITEM_RESERVED = (3 << 1),
+};
+
+}
+
+#endif \ No newline at end of file
diff --git a/Src/apev2/header.cpp b/Src/apev2/header.cpp
new file mode 100644
index 00000000..bb92c784
--- /dev/null
+++ b/Src/apev2/header.cpp
@@ -0,0 +1,88 @@
+#include "header.h"
+#include "flags.h"
+#include "util.h"
+#include <limits.h>
+
+static char preamble[] = { 'A', 'P', 'E', 'T', 'A', 'G', 'E', 'X' };
+
+APEv2::Header::Header(void *data)
+{
+ memcpy(&headerData, data, sizeof(headerData));
+
+ // covert to host endian
+ //headerData.preamble=htons(headerData.preamble);
+ headerData.version=ATON16(headerData.version);
+ headerData.size=ATON32(headerData.size);
+ headerData.items=ATON32(headerData.items);
+ headerData.flags=ATON32(headerData.flags);
+}
+
+APEv2::Header::Header(const HeaderData *data)
+{
+ memcpy(&headerData.preamble, preamble, sizeof(headerData.preamble));
+ headerData.version = NTOA32(2000);
+ headerData.size = data->size;
+ headerData.items = data->items;
+ headerData.flags = data->flags;
+ headerData.reserved = 0;
+}
+
+uint32_t APEv2::Header::GetFlags()
+{
+ return headerData.flags;
+}
+
+bool APEv2::Header::Valid()
+{
+ return !memcmp(&headerData.preamble, preamble, 8) && headerData.reserved == 0;
+}
+
+uint32_t APEv2::Header::TagSize()
+{
+ size_t size = headerData.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()
+{
+ return !!(headerData.flags & FLAG_HEADER_HAS_HEADER);
+}
+
+bool APEv2::Header::HasFooter()
+{
+ return !(headerData.flags & FLAG_HEADER_NO_FOOTER);
+}
+
+bool APEv2::Header::IsFooter()
+{
+ return !(headerData.flags & FLAG_HEADER_IS_HEADER);
+}
+
+bool APEv2::Header::IsHeader()
+{
+ return !!(headerData.flags & FLAG_HEADER_IS_HEADER);
+}
+
+int APEv2::Header::Encode(void *data, size_t len)
+{
+ if (len < 32)
+ return APEV2_TOO_SMALL;
+
+ HeaderData endianCorrectData = headerData;
+
+ endianCorrectData.version=NTOA16(endianCorrectData.version);
+ endianCorrectData.size=NTOA32(endianCorrectData.size);
+ endianCorrectData.items=NTOA32(endianCorrectData.items);
+ endianCorrectData.flags=NTOA32(endianCorrectData.flags);
+
+ memcpy(data, &endianCorrectData, sizeof(endianCorrectData));
+ return APEV2_SUCCESS;
+} \ No newline at end of file
diff --git a/Src/apev2/header.h b/Src/apev2/header.h
new file mode 100644
index 00000000..8f840414
--- /dev/null
+++ b/Src/apev2/header.h
@@ -0,0 +1,44 @@
+#ifndef NULLSOFT_APEV2_HEADER_H
+#define NULLSOFT_APEV2_HEADER_H
+
+#include <bfc/platform/types.h>
+
+namespace APEv2
+{
+
+#pragma pack(push, 1)
+ struct HeaderData
+ {
+ uint64_t preamble;
+ uint32_t version;
+ uint32_t size;
+ uint32_t items;
+ uint32_t flags;
+ uint16_t reserved;
+ };
+#pragma pack(pop)
+
+class Header
+{
+public:
+ Header(void *data);
+ Header(const HeaderData *data);
+ bool Valid();
+ uint32_t TagSize();
+ bool HasHeader();
+ bool HasFooter();
+ bool IsFooter();
+ bool IsHeader();
+ int Encode(void *data, size_t len);
+ uint32_t GetFlags();
+enum
+{
+ SIZE=32,
+};
+private:
+ HeaderData headerData;
+};
+}
+
+
+#endif \ No newline at end of file
diff --git a/Src/apev2/item.cpp b/Src/apev2/item.cpp
new file mode 100644
index 00000000..c757620f
--- /dev/null
+++ b/Src/apev2/item.cpp
@@ -0,0 +1,221 @@
+#include "item.h"
+#include "flags.h"
+#include "util.h"
+#include <strsafe.h>
+#include <stdint.h>
+
+/*
+http://wiki.hydrogenaudio.org/index.php?title=APE_Tag_Item
+*/
+
+APEv2::Item::Item()
+{
+ refCount=1;
+ len=0;
+ flags=0;
+ key=0;
+ value=0;
+}
+
+APEv2::Item::~Item()
+{
+ free(key);
+ free(value);
+}
+
+void APEv2::Item::Retain()
+{
+ refCount++;
+}
+
+void APEv2::Item::Release()
+{
+ if (--refCount == 0)
+ delete this;
+}
+
+int APEv2::Item::Read(void *_data, size_t datalen, void **new_data, size_t *new_len)
+{
+ char *data = (char *)_data;
+
+ if (datalen < 4)
+ return APEV2_TOO_SMALL;
+ memcpy(&len, data, 4);
+ len = ATON32(len);
+ data+=4;
+ datalen-=4;
+
+ if (datalen < 4)
+ return APEV2_TOO_SMALL;
+ memcpy(&flags, data, 4);
+ flags = ATON32(flags);
+ data+=4;
+ datalen-=4;
+
+ uint32_t key_len=0;
+ for (uint32_t i=0;i<datalen;i++)
+ {
+ if (data[i] == 0)
+ {
+ key_len=i;
+ break;
+ }
+ }
+
+ if (key_len == datalen)
+ return APEV2_TOO_SMALL;
+
+ if (key_len == 0)
+ return APEV2_FAILURE;
+
+ if (key)
+ {
+ free(key);
+ key = 0;
+ }
+ key = (char *)calloc(key_len+1, sizeof(char));
+ if (key)
+ {
+ StringCchCopyA(key, key_len+1, data);
+ datalen-=(key_len+1);
+ data+=(key_len+1);
+
+ if (datalen < len)
+ {
+ free(key);
+ key = 0;
+ return APEV2_TOO_SMALL;
+ }
+
+ if (value)
+ {
+ free(value);
+ value = 0;
+ }
+ value = (char *)calloc(len, sizeof(char));
+ if (value)
+ {
+ memcpy(value, data, len);
+ datalen-=len;
+ data+=len;
+ *new_len = datalen;
+ *new_data=data;
+ return APEV2_SUCCESS;
+ }
+ else
+ {
+ free(key);
+ return APEV2_FAILURE;
+ }
+ }
+ else
+ return APEV2_FAILURE;
+}
+
+bool APEv2::Item::IsReadOnly()
+{
+ return flags & FLAG_READONLY;
+}
+
+bool APEv2::Item::IsString()
+{
+ return (flags & MASK_ITEM_TYPE) == FLAG_ITEM_TEXT;
+}
+
+bool APEv2::Item::KeyMatch(const char *key_to_compare, int compare)
+{
+ if (!key || !*key)
+ return false;
+
+ switch (compare)
+ {
+ case ITEM_KEY_COMPARE_CASE_INSENSITIVE:
+ return !_stricmp(key_to_compare, key);
+ case ITEM_KEY_COMPARE_CASE_SENSITIVE:
+ return !strcmp(key_to_compare, key);
+ default:
+ return false;
+ }
+}
+
+int APEv2::Item::Get(void **data, size_t *datalen)
+{
+ if (!value || !len)
+ return APEV2_FAILURE;
+ *data = value;
+ *datalen = len;
+ return APEV2_SUCCESS;
+}
+
+int APEv2::Item::Set(const void *data, size_t datalen, int dataType)
+{
+ if (!data || !datalen)
+ return APEV2_FAILURE;
+
+ // set data type for this item
+ flags &= ~MASK_ITEM_TYPE;
+ flags |= dataType;
+
+ free(value);
+ value = malloc(datalen);
+ len=(uint32_t)datalen;
+ memcpy(value, data, len);
+ return APEV2_SUCCESS;
+}
+
+int APEv2::Item::SetKey(const char *tag)
+{
+ if (!tag || !*tag)
+ return APEV2_FAILURE;
+
+ free(key);
+ key = _strdup(tag);
+ return APEV2_SUCCESS;
+}
+
+int APEv2::Item::GetKey(const char **tag)
+{
+ if (!key)
+ return APEV2_FAILURE;
+ *tag = key;
+ return APEV2_SUCCESS;
+}
+
+size_t APEv2::Item::EncodeSize()
+{
+ return 4 /* size */ + 4 /* flags */ + (key && *key ? strlen(key) : 0) + 1 /* NULL separator */ + len;
+}
+
+int APEv2::Item::Encode(void *data, size_t datalen)
+{
+ if (!key || !value || !len)
+ return APEV2_FAILURE;
+
+ if (datalen < EncodeSize())
+ return APEV2_TOO_SMALL;
+
+ int8_t *ptr = (int8_t *)data;
+
+ // write data length
+ int32_t _len = NTOA32(len);
+ memcpy(ptr, &_len, sizeof(_len));
+ ptr+=sizeof(_len);
+ datalen-=sizeof(_len);
+
+ // write flags
+ int32_t _flags = NTOA32(flags);
+ memcpy(ptr, &_flags, sizeof(_flags));
+ ptr+=sizeof(_flags);
+ datalen-=sizeof(_flags);
+
+ // write key and null terminator
+ if (StringCchCopyExA((char *)ptr, datalen, key, (char **) &ptr, &datalen, 0) != S_OK)
+ return APEV2_FAILURE;
+ // account for null separator
+ ptr++;
+ datalen--;
+
+ // write data
+ memcpy(ptr, value, len);
+ return APEV2_SUCCESS;
+} \ No newline at end of file
diff --git a/Src/apev2/item.h b/Src/apev2/item.h
new file mode 100644
index 00000000..87ded0b1
--- /dev/null
+++ b/Src/apev2/item.h
@@ -0,0 +1,43 @@
+#ifndef NULLSOFT_APEV2_ITEM_H
+#define NULLSOFT_APEV2_ITEM_H
+
+#include <bfc/platform/types.h>
+
+namespace APEv2
+{
+enum
+{
+ ITEM_KEY_COMPARE_CASE_INSENSITIVE = 0,
+ ITEM_KEY_COMPARE_CASE_SENSITIVE = 1,
+};
+class Item
+{
+public:
+ Item();
+ ~Item();
+ void Retain();
+ void Release();
+
+ /* If successful, puts incremented data pointer in new_data, and new data size remaining in new_len */
+ int Read(void *data, size_t len, void **new_data, size_t *new_len);
+
+ int Encode(void *data, size_t len);
+ size_t EncodeSize();
+
+ bool IsReadOnly();
+ bool IsString();
+ bool KeyMatch(const char *key, int compare=ITEM_KEY_COMPARE_CASE_INSENSITIVE);
+ int Get(void **data, size_t *len);
+ int GetKey(const char **tag);
+ int Set(const void *data, size_t len, int dataType);
+ int SetKey(const char *tag);
+
+private:
+ size_t refCount;
+ uint32_t flags;
+ char *key;
+ void *value;
+ uint32_t len;
+};
+}
+#endif
diff --git a/Src/apev2/tag.cpp b/Src/apev2/tag.cpp
new file mode 100644
index 00000000..8d2edb47
--- /dev/null
+++ b/Src/apev2/tag.cpp
@@ -0,0 +1,353 @@
+#include "tag.h"
+#include "header.h"
+#include "flags.h"
+#include "nu/ns_wc.h"
+#include <limits.h>
+#include <strsafe.h>
+#include <stdint.h>
+
+/*
+http://wiki.hydrogenaudio.org/index.php?title=APEv2_specification
+*/
+
+APEv2::Tag::Tag()
+{
+ flags = 0; // default to writing just a footer
+}
+
+int APEv2::Tag::Parse(const void *_data, size_t len)
+{
+ char *data = (char *)_data;
+
+ if (len < Header::SIZE)
+ return APEV2_TOO_SMALL;
+
+ char *headerStart = data+(len-Header::SIZE);
+
+ Header footer(headerStart);
+ if (footer.Valid()
+ && !(len == Header::SIZE && footer.IsHeader())) // if all we have is this footer, we should check that it's not really a header
+ {
+ len -= Header::SIZE;
+ char *dataStart = data;
+ if (footer.HasHeader())
+ {
+ // TODO: validate header
+ dataStart+= Header::SIZE;
+ len-=Header::SIZE;
+ }
+ flags = footer.GetFlags();
+ flags &= ~FLAG_HEADER_NO_FOOTER; // winamp 5.54 had this flag reversed, so let's correct it
+ return ParseData(dataStart, len);
+ }
+
+ // ok, we didn't have a footer, so let's see if we have a header
+ headerStart = data;
+ Header header(headerStart);
+ if (header.Valid())
+ {
+ len -= Header::SIZE;
+ char *dataStart = data + Header::SIZE;
+ if (header.HasFooter())
+ {
+ // TODO: validate footer
+ // benski> cut... we got here because we didn't have a footer.
+ //len-=Header::SIZE;
+ }
+ flags = header.GetFlags();
+ flags |= FLAG_HEADER_NO_FOOTER; // winamp 5.54 had this flag reversed, so let's correct it
+ return ParseData(dataStart, len);
+ }
+
+ return APEV2_FAILURE;
+}
+
+int APEv2::Tag::ParseData(const void *data, size_t len)
+{
+ char *dataStart = (char *)data;
+ while (len)
+ {
+ Item *item = new Item;
+ size_t new_len = 0;
+ void *new_data = 0;
+ int ret = item->Read(dataStart, len, &new_data, &new_len);
+ if (ret == APEV2_SUCCESS)
+ {
+ len = new_len;
+ dataStart = (char *)new_data;
+ items.push_back(item);
+ }
+ else
+ {
+ item->Release();
+ return ret;
+ }
+ }
+ return APEV2_SUCCESS;
+}
+
+int APEv2::Tag::GetString(const char *tag, wchar_t *data, size_t dataLen)
+{
+ /* our UTF-8 to wchar_t conversion function wants an int for size, so we should explicitly check the size */
+ if (dataLen > INT_MAX)
+ return APEV2_FAILURE;
+
+ for ( APEv2::Item *l_item : items )
+ {
+ /* check if it's a string first, and then match the key next (will be faster) */
+ if ( l_item->IsString() && l_item->KeyMatch(tag, ITEM_KEY_COMPARE_CASE_INSENSITIVE))
+ {
+ void *item_data = 0;
+ size_t item_dataLen = 0;
+ if ( l_item->Get(&item_data, &item_dataLen) == APEV2_SUCCESS && item_dataLen < INT_MAX)
+ {
+ int signed_len = static_cast<int>(item_dataLen); // we checked against INT_MAX above so this conversion is safe
+ if (MultiByteToWideCharSZ(CP_UTF8, 0, (LPCSTR)item_data, signed_len, data, (int)dataLen) != 0)
+ return APEV2_SUCCESS;
+ }
+ return APEV2_NO_DATA;
+ }
+ }
+
+ return APEV2_KEY_NOT_FOUND;
+}
+
+int APEv2::Tag::SetItemData(APEv2::Item *item, const wchar_t *data)
+{
+ int utf16_data_cch = (int)wcslen(data);
+ int item_dataLen = WideCharToMultiByte(CP_UTF8, 0, data, utf16_data_cch, 0, 0, NULL, NULL);
+ if (!item_dataLen)
+ return APEV2_FAILURE;
+
+ char *item_data = (char *)calloc(item_dataLen, sizeof(char));
+ if (item_data)
+ {
+ if (!WideCharToMultiByte(CP_UTF8, 0, data, utf16_data_cch, item_data, item_dataLen, NULL, NULL))
+ {
+ free(item_data);
+ return APEV2_FAILURE;
+ }
+
+ item->Set(item_data, item_dataLen, FLAG_ITEM_TEXT);
+ free(item_data);
+ return APEV2_SUCCESS;
+ }
+ return APEV2_FAILURE;
+}
+
+int APEv2::Tag::SetString(const char *tag, const wchar_t *data)
+{
+ for (auto it = items.begin(); it != items.end(); it++)
+ {
+ APEv2::Item* item = *it;
+ if (item->KeyMatch(tag, ITEM_KEY_COMPARE_CASE_INSENSITIVE))
+ {
+ if (data && *data)
+ {
+ return SetItemData(item, data);
+ }
+ else
+ {
+ item->Release();
+ it = items.erase(it);
+ return APEV2_SUCCESS;
+ }
+ }
+ }
+
+ if (data && *data)
+ {
+ /* Not already in the list, so we need to add it */
+ Item *newItem = new Item;
+
+ if (newItem->SetKey(tag) == APEV2_SUCCESS && SetItemData(newItem, data) == APEV2_SUCCESS)
+ {
+ items.push_back(newItem);
+ }
+ else
+ {
+ newItem->Release();
+ return APEV2_FAILURE;
+ }
+ }
+
+ return APEV2_SUCCESS;
+}
+
+int APEv2::Tag::EnumValue(size_t i, const char **tag, wchar_t *data, size_t dataLen)
+{
+ /* our UTF-8 to wchar_t conversion function wants an int for size, so we should explicitly check the size */
+ if (dataLen > INT_MAX)
+ return APEV2_FAILURE;
+
+ if (i >= items.size())
+ return APEV2_END_OF_ITEMS;
+
+ APEv2::Item *item = items[i];
+ item->GetKey(tag);
+
+ if (!data || !dataLen) // if they don't want the data, go ahead and return now
+ return APEV2_SUCCESS;
+
+ data[0]=0;
+ void *item_data = 0;
+ size_t item_dataLen = 0;
+ if (item->IsString())
+ {
+ if (item->Get(&item_data, &item_dataLen) == APEV2_SUCCESS && item_dataLen < INT_MAX)
+ {
+ int signed_len = static_cast<int>(item_dataLen); // we checked against INT_MAX above so this conversion is safe
+ if (MultiByteToWideCharSZ(CP_UTF8, 0, (LPCSTR)item_data, signed_len, data, (int)dataLen) == 0)
+ *data=0;
+ }
+ }
+ else
+ {
+ // TODO: benski> hmmm
+ StringCchCopyW(data, dataLen, L"[TODO: deal with binary]");
+ }
+
+ return APEV2_SUCCESS;
+}
+
+void APEv2::Tag::Clear()
+{
+ for ( APEv2::Item *l_item : items )
+ {
+ l_item->Release();
+ }
+
+ items.clear();
+}
+
+int APEv2::Tag::RemoveItem(size_t index)
+{
+ if (index >= items.size())
+ return APEV2_END_OF_ITEMS;
+
+ APEv2::Item *item = items[index];
+ items.erase(items.begin() + index);
+ item->Release();
+ return APEV2_SUCCESS;
+}
+
+size_t APEv2::Tag::EncodeSize()
+{
+ size_t totalSize=0;
+
+ if (flags & FLAG_HEADER_HAS_HEADER)
+ totalSize+=Header::SIZE;
+
+ for ( APEv2::Item *l_item : items )
+ {
+ totalSize += l_item->EncodeSize();
+ }
+
+ if (!(flags & FLAG_HEADER_NO_FOOTER))
+ totalSize+=Header::SIZE;
+
+ return totalSize;
+}
+
+int APEv2::Tag::Encode(void *data, size_t len)
+{
+ size_t totalSize=0;
+ int8_t *ptr = (int8_t *)data;
+
+ if (flags & FLAG_HEADER_HAS_HEADER)
+ {
+ HeaderData headerData = {0};
+ headerData.size= (uint32_t)totalSize;
+ if (!(flags & FLAG_HEADER_NO_FOOTER))
+ headerData.size += Header::SIZE;
+ headerData.items = (uint32_t)items.size();
+
+ headerData.flags = (flags & FLAG_HEADER_ENCODE_MASK)|FLAG_HEADER_IS_HEADER;
+
+ Header header(&headerData);
+ int ret = header.Encode(ptr, len);
+ if (ret != APEV2_SUCCESS)
+ return ret;
+ ptr += Header::SIZE;
+ len -= Header::SIZE;
+ }
+
+ for ( APEv2::Item *l_item : items )
+ {
+ int ret = l_item->Encode(ptr, len);
+ if (ret!= APEV2_SUCCESS)
+ return ret;
+ size_t itemSize = l_item->EncodeSize();
+ len-=itemSize;
+ ptr+=itemSize;
+ totalSize+=itemSize;
+ }
+
+ if (!(flags & FLAG_HEADER_NO_FOOTER))
+ {
+ HeaderData footerData = {0};
+ footerData.size= (uint32_t)totalSize + Header::SIZE;
+ footerData.items = (uint32_t)items.size();
+ footerData.flags = (flags & FLAG_HEADER_ENCODE_MASK);
+
+ Header footer(&footerData);
+ int ret = footer.Encode(ptr, len);
+ if (ret != APEV2_SUCCESS)
+ return ret;
+ }
+ return APEV2_SUCCESS;
+}
+
+int APEv2::Tag::SetKeyValueByIndex(size_t index, const char *key, const wchar_t *data)
+{
+ if (index >= items.size())
+ return APEV2_END_OF_ITEMS;
+
+ // TODO: check for duplicate key
+
+ items[index]->SetKey(key);
+ return SetItemData(items[index], data);
+}
+
+APEv2::Item *APEv2::Tag::AddItem()
+{
+ Item *newItem = new Item;
+ items.push_back(newItem);
+ return newItem;
+}
+
+int APEv2::Tag::SetFlags(uint32_t newflags, uint32_t mask)
+{
+ flags = (flags & ~mask) | newflags;
+ return APEV2_SUCCESS;
+}
+
+int APEv2::Tag::FindItemByKey(const char *key, size_t *index, int compare)
+{
+ for (size_t i=0;i!=items.size();i++)
+ {
+ if (items[i]->KeyMatch(key, compare))
+ {
+ *index = i;
+ return APEV2_SUCCESS;
+ }
+ }
+ return APEV2_KEY_NOT_FOUND;
+}
+
+size_t APEv2::Tag::GetNumItems()
+{
+ return items.size();
+}
+
+bool APEv2::Tag::IsReadOnly()
+{
+ return flags & FLAG_READONLY;
+}
+
+bool APEv2::Tag::IsItemReadOnly(size_t index)
+{
+ if (index >= items.size())
+ return true;
+ return items[index]->IsReadOnly();
+} \ No newline at end of file
diff --git a/Src/apev2/tag.h b/Src/apev2/tag.h
new file mode 100644
index 00000000..0a3cd082
--- /dev/null
+++ b/Src/apev2/tag.h
@@ -0,0 +1,55 @@
+#ifndef NULLSOFT_APEV2_TAG_H
+#define NULLSOFT_APEV2_TAG_H
+
+#include <bfc/platform/types.h>
+#include <vector>
+#include "item.h"
+#include "flags.h"
+
+namespace APEv2
+{
+class Tag
+{
+public:
+ Tag();
+
+ /* Parsing */
+ int Parse(const void *data, size_t len);
+
+ /* Retrieving Data */
+ int GetString(const char *tag, wchar_t *data, size_t dataLen);
+ int EnumValue(size_t i, const char **tag, wchar_t *data, size_t dataLen);
+ int FindItemByKey(const char *key, size_t *index, int compare=ITEM_KEY_COMPARE_CASE_INSENSITIVE);
+ size_t GetNumItems();
+ bool IsReadOnly();
+ bool IsItemReadOnly(size_t index);
+
+ /* Setting Data */
+ Item *AddItem();
+ int SetString(const char *tag, const wchar_t *data);
+ int SetKeyValueByIndex(size_t index, const char *key, const wchar_t *data);
+ int SetFlags(uint32_t flags, uint32_t mask);
+
+ /* Removing Data */
+ void Clear();
+ int RemoveItem(size_t index);
+ int RemoveItem(const char *tag);
+
+ /* Serializing */
+ size_t EncodeSize();
+ int Encode(void *data, size_t len);
+
+
+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. */
+
+protected:
+ int SetItemData(APEv2::Item *item, const wchar_t *data);
+
+private: /* data */
+ std::vector<APEv2::Item*> items;
+ uint32_t flags;
+};
+}
+
+#endif
diff --git a/Src/apev2/util.h b/Src/apev2/util.h
new file mode 100644
index 00000000..0c185a12
--- /dev/null
+++ b/Src/apev2/util.h
@@ -0,0 +1,22 @@
+#ifndef NULLSOFT_APEV2_UTIL_H
+#define NULLSOFT_APEV2_UTIL_H
+
+#if defined(BIG_ENDIAN)
+#define ATON16(A) ((((uint16_t)(A) & 0xff00) >> 8) | \
+ (((uint16_t)(A) & 0x00ff) << 8))
+#define ATON32(A) ((((uint32_t)(A) & 0xff000000) >> 25) | \
+ (((uint32_t)(A) & 0x00ff0000) >> 8) | \
+ (((uint32_t)(A) & 0x0000ff00) << 8) | \
+ (((uint32_t)(A) & 0x000000ff) << 24))
+
+#elif defined(LITTLE_ENDIAN) || defined(_M_IX86) || defined(_M_X64) || defined(_WIN64)
+#define ATON16(A) (A)
+#define ATON32(A) (A)
+#else
+#error neither BIG_ENDIAN nor LITTLE_ENDIAN defined!
+#endif
+
+#define NTOA32(N) ATON32(N)
+#define NTOA16(N) ATON16(N)
+
+#endif \ No newline at end of file