diff options
Diffstat (limited to 'Src/apev2')
-rw-r--r-- | Src/apev2/apev2.sln | 30 | ||||
-rw-r--r-- | Src/apev2/apev2.vcxproj | 195 | ||||
-rw-r--r-- | Src/apev2/apev2.vcxproj.filters | 42 | ||||
-rw-r--r-- | Src/apev2/flags.h | 40 | ||||
-rw-r--r-- | Src/apev2/header.cpp | 88 | ||||
-rw-r--r-- | Src/apev2/header.h | 44 | ||||
-rw-r--r-- | Src/apev2/item.cpp | 221 | ||||
-rw-r--r-- | Src/apev2/item.h | 43 | ||||
-rw-r--r-- | Src/apev2/tag.cpp | 353 | ||||
-rw-r--r-- | Src/apev2/tag.h | 55 | ||||
-rw-r--r-- | Src/apev2/util.h | 22 |
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 |