diff --git a/.gitignore b/.gitignore index 23107a9..d2cf558 100644 --- a/.gitignore +++ b/.gitignore @@ -42,10 +42,7 @@ bld/ .vs/ # Libraries (binaries that aren't in the repo) -Lib - -# RT64 (since it's not public yet) -RT64 +lib/ # Runtime files imgui.ini diff --git a/MMRecomp.vcxproj b/MMRecomp.vcxproj index 31a4d3a..8696d88 100644 --- a/MMRecomp.vcxproj +++ b/MMRecomp.vcxproj @@ -17,7 +17,6 @@ Release x64 - 16.0 @@ -53,25 +52,23 @@ true Unicode - - + + + + + + + + + + + + + - - - - - - - - - - - - - true @@ -85,18 +82,24 @@ false - Level3 true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true + $(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories) + stdcpp20 Console true + $(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies) + + XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y +XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y + @@ -106,13 +109,20 @@ true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true + $(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories) + stdcpp20 Console true true true + $(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies) + + XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y +XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y + @@ -120,11 +130,18 @@ true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true + $(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories) + stdcpp20 Console true + $(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies) + + XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y +XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y + @@ -134,17 +151,83 @@ true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true + $(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories) + stdcpp20 Console true true true + $(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies) + + XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y +XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y + - - + + + + + + + + + + + + + + + MaxSpeed + MaxSpeed + Default + Default + + + MaxSpeed + MaxSpeed + Default + Default + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {7bf5e3f9-c49f-4c84-ab64-7681f028cac2} + + - + \ No newline at end of file diff --git a/MMRecomp.vcxproj.filters b/MMRecomp.vcxproj.filters index a8a6563..0c0a0e6 100644 --- a/MMRecomp.vcxproj.filters +++ b/MMRecomp.vcxproj.filters @@ -14,4 +14,134 @@ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + \ No newline at end of file diff --git a/RecompiledFuncs.vcxproj b/RecompiledFuncs.vcxproj index 6c18663..9d05f96 100644 --- a/RecompiledFuncs.vcxproj +++ b/RecompiledFuncs.vcxproj @@ -113,8 +113,11 @@ WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true NotUsing - true + + true + false + @@ -149,8 +152,11 @@ NDEBUG;_LIB;%(PreprocessorDefinitions) true NotUsing - true + + true + false + diff --git a/RecompiledFuncs.vcxproj.filters b/RecompiledFuncs.vcxproj.filters index 37df2e0..83274f8 100644 --- a/RecompiledFuncs.vcxproj.filters +++ b/RecompiledFuncs.vcxproj.filters @@ -15,5 +15,16952 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/include/rsp.h b/include/rsp.h new file mode 100644 index 0000000..a5e53fb --- /dev/null +++ b/include/rsp.h @@ -0,0 +1,65 @@ +#ifndef __RSP_H__ +#define __RSP_H__ + +#include "rsp_vu.h" +#include "recomp.h" + +enum class RspExitReason { + Invalid, + Broke, + ImemOverrun, + UnhandledJumpTarget +}; + +extern uint8_t dmem[]; +extern uint16_t rspReciprocals[512]; +extern uint16_t rspInverseSquareRoots[512]; + +#define RSP_MEM_W(offset, addr) \ + (*reinterpret_cast(dmem + (offset) + (addr))) + +#define RSP_MEM_H(offset, addr) \ + (*reinterpret_cast(dmem + (((offset) + (addr)) ^ 2))) + +#define RSP_MEM_HU(offset, addr) \ + (*reinterpret_cast(dmem + (((offset) + (addr)) ^ 2))) + +#define RSP_MEM_B(offset, addr) \ + (*reinterpret_cast(dmem + (((offset) + (addr)) ^ 3))) + +#define RSP_MEM_BU(offset, addr) \ + (*reinterpret_cast(dmem + (((offset) + (addr)) ^ 3))) + +#define RSP_ADD32(a, b) \ + ((int32_t)((a) + (b))) + +#define RSP_SUB32(a, b) \ + ((int32_t)((a) - (b))) + +#define RSP_SIGNED(val) \ + ((int32_t)(val)) + +#define SET_DMA_DMEM(dmem_addr) dma_dmem_address = (dmem_addr) +#define SET_DMA_DRAM(dram_addr) dma_dram_address = (dram_addr) +#define DO_DMA_READ(rd_len) dma_rdram_to_dmem(rdram, dma_dmem_address, dma_dram_address, (rd_len)) +#define DO_DMA_WRITE(wr_len) dma_dmem_to_rdram(rdram, dma_dmem_address, dma_dram_address, (wr_len)) + +static inline void dma_rdram_to_dmem(uint8_t* rdram, uint32_t dmem_addr, uint32_t dram_addr, uint32_t rd_len) { + rd_len += 1; // Read length is inclusive + dram_addr &= 0xFFFFF8; + assert(dmem_addr + rd_len <= 0x1000); + for (uint32_t i = 0; i < rd_len; i++) { + RSP_MEM_B(i, dmem_addr) = MEM_B(0, (int64_t)(int32_t)(dram_addr + i + 0x80000000)); + } +} + +static inline void dma_dmem_to_rdram(uint8_t* rdram, uint32_t dmem_addr, uint32_t dram_addr, uint32_t wr_len) { + wr_len += 1; // Write length is inclusive + dram_addr &= 0xFFFFF8; + assert(dmem_addr + wr_len <= 0x1000); + for (uint32_t i = 0; i < wr_len; i++) { + MEM_B(0, (int64_t)(int32_t)(dram_addr + i + 0x80000000)) = RSP_MEM_B(i, dmem_addr); + } +} + +#endif diff --git a/include/rsp_vu.h b/include/rsp_vu.h new file mode 100644 index 0000000..d60e62f --- /dev/null +++ b/include/rsp_vu.h @@ -0,0 +1,199 @@ +// This file is modified from the Ares N64 emulator core. Ares can +// be found at https://github.com/ares-emulator/ares. The original license +// for this portion of Ares is as follows: +// ---------------------------------------------------------------------- +// ares +// +// Copyright(c) 2004 - 2021 ares team, Near et al +// +// Permission to use, copy, modify, and /or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright noticeand this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS.IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// ---------------------------------------------------------------------- +#include + +#define ARCHITECTURE_AMD64 +#define ARCHITECTURE_SUPPORTS_SSE4_1 1 + +#if defined(ARCHITECTURE_AMD64) +#include +using v128 = __m128i; +#elif defined(ARCHITECTURE_ARM64) +#include +using v128 = __m128i; +#endif + +namespace Accuracy { + namespace RSP { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + constexpr bool SISD = false; + constexpr bool SIMD = true; +#else + constexpr bool SISD = true; + constexpr bool SIMD = false; +#endif + } +} + +using u8 = uint8_t; +using s8 = int8_t; +using u16 = uint16_t; +using s16 = int16_t; +using u32 = uint32_t; +using s32 = int32_t; +using u64 = uint64_t; +using s64 = int64_t; +using uint128_t = uint64_t[2]; + +template inline auto sclamp(s64 x) -> s64 { + enum : s64 { b = 1ull << (bits - 1), m = b - 1 }; + return (x > m) ? m : (x < -b) ? -b : x; +} + +struct RSP { + using r32 = uint32_t; + using cr32 = const r32; + + union r128 { + struct { uint64_t u128[2]; }; +#if ARCHITECTURE_SUPPORTS_SSE4_1 + struct { __m128i v128; }; + + operator __m128i() const { return v128; } + auto operator=(__m128i value) { v128 = value; } +#endif + + auto byte(u32 index) -> uint8_t& { return ((uint8_t*)&u128)[15 - index]; } + auto byte(u32 index) const -> uint8_t { return ((uint8_t*)&u128)[15 - index]; } + + auto element(u32 index) -> uint16_t& { return ((uint16_t*)&u128)[7 - index]; } + auto element(u32 index) const -> uint16_t { return ((uint16_t*)&u128)[7 - index]; } + + auto u8(u32 index) -> uint8_t& { return ((uint8_t*)&u128)[15 - index]; } + auto u8(u32 index) const -> uint8_t { return ((uint8_t*)&u128)[15 - index]; } + + auto s16(u32 index) -> int16_t& { return ((int16_t*)&u128)[7 - index]; } + auto s16(u32 index) const -> int16_t { return ((int16_t*)&u128)[7 - index]; } + + auto u16(u32 index) -> uint16_t& { return ((uint16_t*)&u128)[7 - index]; } + auto u16(u32 index) const -> uint16_t { return ((uint16_t*)&u128)[7 - index]; } + + //VCx registers + auto get(u32 index) const -> bool { return u16(index) != 0; } + auto set(u32 index, bool value) -> bool { return u16(index) = 0 - value, value; } + + //vu-registers.cpp + inline auto operator()(u32 index) const -> r128; + }; + using cr128 = const r128; + + struct VU { + r128 r[32]; + r128 acch, accm, accl; + r128 vcoh, vcol; //16-bit little endian + r128 vcch, vccl; //16-bit little endian + r128 vce; // 8-bit little endian + s16 divin; + s16 divout; + bool divdp; + } vpu; + + static constexpr r128 zero{0}; + static constexpr r128 invert{(uint64_t)-1, (uint64_t)-1}; + + inline auto accumulatorGet(u32 index) const -> u64; + inline auto accumulatorSet(u32 index, u64 value) -> void; + inline auto accumulatorSaturate(u32 index, bool slice, u16 negative, u16 positive) const -> u16; + + inline auto CFC2(r32& rt, u8 rd) -> void; + inline auto CTC2(cr32& rt, u8 rd) -> void; + template inline auto LBV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LDV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LFV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LHV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LLV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LPV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LQV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LRV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LSV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LTV(u8 vt, cr32& rs, s8 imm) -> void; + template inline auto LUV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LWV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto MFC2(r32& rt, cr128& vs) -> void; + template inline auto MTC2(cr32& rt, r128& vs) -> void; + template inline auto SBV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SDV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SFV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SHV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SLV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SPV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SQV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SRV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SSV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto STV(u8 vt, cr32& rs, s8 imm) -> void; + template inline auto SUV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SWV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto VABS(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VADD(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VADDC(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VAND(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VCH(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VCL(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VCR(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VEQ(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VGE(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VLT(r128& vd, cr128& vs, cr128& vt) -> void; + template + inline auto VMACF(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMACF(r128& vd, cr128& vs, cr128& vt) -> void { VMACF<0, e>(vd, vs, vt); } + template inline auto VMACU(r128& vd, cr128& vs, cr128& vt) -> void { VMACF<1, e>(vd, vs, vt); } + inline auto VMACQ(r128& vd) -> void; + template inline auto VMADH(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMADL(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMADM(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMADN(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMOV(r128& vd, u8 de, cr128& vt) -> void; + template inline auto VMRG(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMUDH(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMUDL(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMUDM(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMUDN(r128& vd, cr128& vs, cr128& vt) -> void; + template + inline auto VMULF(r128& rd, cr128& vs, cr128& vt) -> void; + template inline auto VMULF(r128& rd, cr128& vs, cr128& vt) -> void { VMULF<0, e>(rd, vs, vt); } + template inline auto VMULU(r128& rd, cr128& vs, cr128& vt) -> void { VMULF<1, e>(rd, vs, vt); } + template inline auto VMULQ(r128& rd, cr128& vs, cr128& vt) -> void; + template inline auto VNAND(r128& rd, cr128& vs, cr128& vt) -> void; + template inline auto VNE(r128& vd, cr128& vs, cr128& vt) -> void; + inline auto VNOP() -> void; + template inline auto VNOR(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VNXOR(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VOR(r128& vd, cr128& vs, cr128& vt) -> void; + template + inline auto VRCP(r128& vd, u8 de, cr128& vt) -> void; + template inline auto VRCP(r128& vd, u8 de, cr128& vt) -> void { VRCP<0, e>(vd, de, vt); } + template inline auto VRCPL(r128& vd, u8 de, cr128& vt) -> void { VRCP<1, e>(vd, de, vt); } + template inline auto VRCPH(r128& vd, u8 de, cr128& vt) -> void; + template + inline auto VRND(r128& vd, u8 vs, cr128& vt) -> void; + template inline auto VRNDN(r128& vd, u8 vs, cr128& vt) -> void { VRND<0, e>(vd, vs, vt); } + template inline auto VRNDP(r128& vd, u8 vs, cr128& vt) -> void { VRND<1, e>(vd, vs, vt); } + template + inline auto VRSQ(r128& vd, u8 de, cr128& vt) -> void; + template inline auto VRSQ(r128& vd, u8 de, cr128& vt) -> void { VRSQ<0, e>(vd, de, vt); } + template inline auto VRSQL(r128& vd, u8 de, cr128& vt) -> void { VRSQ<1, e>(vd, de, vt); } + template inline auto VRSQH(r128& vd, u8 de, cr128& vt) -> void; + template inline auto VSAR(r128& vd, cr128& vs) -> void; + template inline auto VSUB(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VSUBC(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VXOR(r128& rd, cr128& vs, cr128& vt) -> void; + template inline auto VZERO(r128& rd, cr128& vs, cr128& vt) -> void; +}; diff --git a/include/rsp_vu_impl.h b/include/rsp_vu_impl.h new file mode 100644 index 0000000..8c22d14 --- /dev/null +++ b/include/rsp_vu_impl.h @@ -0,0 +1,1537 @@ +// This file is modified from the Ares N64 emulator core. Ares can +// be found at https://github.com/ares-emulator/ares. The original license +// for this portion of Ares is as follows: +// ---------------------------------------------------------------------- +// ares +// +// Copyright(c) 2004 - 2021 ares team, Near et al +// +// Permission to use, copy, modify, and /or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright noticeand this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS.IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// ---------------------------------------------------------------------- + +#include +#include +using u32 = uint32_t; + +#define ACCH vpu.acch +#define ACCM vpu.accm +#define ACCL vpu.accl +#define VCOH vpu.vcoh +#define VCOL vpu.vcol +#define VCCH vpu.vcch +#define VCCL vpu.vccl +#define VCE vpu.vce + +#define DIVIN vpu.divin +#define DIVOUT vpu.divout +#define DIVDP vpu.divdp + +auto RSP::r128::operator()(u32 index) const -> r128 { + if constexpr (Accuracy::RSP::SISD) { + r128 v{ *this }; + switch (index) { + case 0: break; + case 1: break; + case 2: v.u16(1) = v.u16(0); v.u16(3) = v.u16(2); v.u16(5) = v.u16(4); v.u16(7) = v.u16(6); break; + case 3: v.u16(0) = v.u16(1); v.u16(2) = v.u16(3); v.u16(4) = v.u16(5); v.u16(6) = v.u16(7); break; + case 4: v.u16(1) = v.u16(2) = v.u16(3) = v.u16(0); v.u16(5) = v.u16(6) = v.u16(7) = v.u16(4); break; + case 5: v.u16(0) = v.u16(2) = v.u16(3) = v.u16(1); v.u16(4) = v.u16(6) = v.u16(7) = v.u16(5); break; + case 6: v.u16(0) = v.u16(1) = v.u16(3) = v.u16(2); v.u16(4) = v.u16(5) = v.u16(7) = v.u16(6); break; + case 7: v.u16(0) = v.u16(1) = v.u16(2) = v.u16(3); v.u16(4) = v.u16(5) = v.u16(6) = v.u16(7); break; + case 8: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(0); break; + case 9: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(1); break; + case 10: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(2); break; + case 11: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(3); break; + case 12: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(4); break; + case 13: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(5); break; + case 14: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(6); break; + case 15: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(7); break; + } + return v; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + static const __m128i shuffle[16] = { + //vector + _mm_set_epi8(15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567 + _mm_set_epi8(15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567 + //scalar quarter + _mm_set_epi8(15,14,15,14,11,10,11,10, 7, 6, 7, 6, 3, 2, 3, 2), //00224466 + _mm_set_epi8(13,12,13,12, 9, 8, 9, 8, 5, 4, 5, 4, 1, 0, 1, 0), //11335577 + //scalar half + _mm_set_epi8(15,14,15,14,15,14,15,14, 7, 6, 7, 6, 7, 6, 7, 6), //00004444 + _mm_set_epi8(13,12,13,12,13,12,13,12, 5, 4, 5, 4, 5, 4, 5, 4), //11115555 + _mm_set_epi8(11,10,11,10,11,10,11,10, 3, 2, 3, 2, 3, 2, 3, 2), //22226666 + _mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 1, 0, 1, 0, 1, 0, 1, 0), //33337777 + //scalar whole + _mm_set_epi8(15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14), //00000000 + _mm_set_epi8(13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12), //11111111 + _mm_set_epi8(11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10), //22222222 + _mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8), //33333333 + _mm_set_epi8(7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6), //44444444 + _mm_set_epi8(5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4), //55555555 + _mm_set_epi8(3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2), //66666666 + _mm_set_epi8(1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0), //77777777 + }; + //todo: benchmark to see if testing for cases 0&1 to return value directly is faster + r128 ret; + ret.v128 = _mm_shuffle_epi8(v128, shuffle[index]); + return ret; +#endif + } +} + +auto RSP::accumulatorGet(u32 index) const -> u64 { + return (u64)ACCH.u16(index) << 32 | (u64)ACCM.u16(index) << 16 | (u64)ACCL.u16(index) << 0; +} + +auto RSP::accumulatorSet(u32 index, u64 value) -> void { + ACCH.u16(index) = value >> 32; + ACCM.u16(index) = value >> 16; + ACCL.u16(index) = value >> 0; +} + +auto RSP::accumulatorSaturate(u32 index, bool slice, u16 negative, u16 positive) const -> u16 { + if (ACCH.s16(index) < 0) { + if (ACCH.u16(index) != 0xffff) return negative; + if (ACCM.s16(index) >= 0) return negative; + } else { + if (ACCH.u16(index) != 0x0000) return positive; + if (ACCM.s16(index) < 0) return positive; + } + return !slice ? ACCL.u16(index) : ACCM.u16(index); +} + +auto RSP::CFC2(r32& rt, u8 rd) -> void { + r128 hi, lo; + switch (rd & 3) { + case 0x00: hi = VCOH; lo = VCOL; break; + case 0x01: hi = VCCH; lo = VCCL; break; + case 0x02: hi = zero; lo = VCE; break; + case 0x03: hi = zero; lo = VCE; break; //unverified + } + + if constexpr (Accuracy::RSP::SISD) { + rt = 0; + for (u32 n = 0; n < 8; n++) { + rt |= lo.get(n) << 0 + n; + rt |= hi.get(n) << 8 + n; + } + rt = s16(rt); + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + static const v128 reverse = _mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + rt = s16(_mm_movemask_epi8(_mm_shuffle_epi8(_mm_packs_epi16(hi, lo), reverse))); +#endif + } +} + +auto RSP::CTC2(cr32& rt, u8 rd) -> void { + r128* hi; r128* lo; + r128 null; + switch (rd & 3) { + case 0x00: hi = &VCOH; lo = &VCOL; break; + case 0x01: hi = &VCCH; lo = &VCCL; break; + case 0x02: hi = &null; lo = &VCE; break; + case 0x03: hi = &null; lo = &VCE; break; //unverified + } + + if constexpr (Accuracy::RSP::SISD) { + for (u32 n = 0; n < 8; n++) { + lo->set(n, rt & 1 << 0 + n); + hi->set(n, rt & 1 << 8 + n); + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + static const v128 mask = _mm_set_epi16(0x0101, 0x0202, 0x0404, 0x0808, 0x1010, 0x2020, 0x4040, 0x8080); + lo->v128 = _mm_cmpeq_epi8(_mm_and_si128(_mm_shuffle_epi8(r128{ ~rt >> 0 }, zero), mask), zero); + hi->v128 = _mm_cmpeq_epi8(_mm_and_si128(_mm_shuffle_epi8(r128{ ~rt >> 8 }, zero), mask), zero); +#endif + } +} + +template +auto RSP::LBV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm; + vt.byte(e) = RSP_MEM_B(0, address); +} + +template +auto RSP::LDV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 8; + auto start = e; + auto end = std::min(start + 8, 16); + for (u32 offset = start; offset < end; offset++) { + vt.byte(offset & 15) = RSP_MEM_B(0, address++); + } +} + +template +auto RSP::LFV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto index = (address & 7) - e; + address &= ~7; + auto start = e; + auto end = std::min(start + 8, 16); + r128 tmp; + for (u32 offset = 0; offset < 4; offset++) { + tmp.element(offset + 0) = RSP_MEM_B(0, address + (index + offset * 4 + 0 & 15)) << 7; + tmp.element(offset + 4) = RSP_MEM_B(0, address + (index + offset * 4 + 8 & 15)) << 7; + } + for (u32 offset = start; offset < end; offset++) { + vt.byte(offset) = tmp.byte(offset); + } +} + +template +auto RSP::LHV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto index = (address & 7) - e; + address &= ~7; + for (u32 offset = 0; offset < 8; offset++) { + vt.element(offset) = RSP_MEM_B(0, address + (index + offset * 2 & 15)) << 7; + } +} + +template +auto RSP::LLV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 4; + auto start = e; + auto end = std::min(start + 4, 16); + for (u32 offset = start; offset < end; offset++) { + vt.byte(offset & 15) = RSP_MEM_B(0, address++); + } +} + +template +auto RSP::LPV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 8; + auto index = (address & 7) - e; + address &= ~7; + for (u32 offset = 0; offset < 8; offset++) { + vt.element(offset) = RSP_MEM_B(0, address + (index + offset & 15)) << 8; + } +} + +template +auto RSP::LQV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto start = e; + auto end = std::min((u32)(16 + e - (address & 15)), (u32)16); + for (u32 offset = start; offset < end; offset++) { + vt.byte(offset & 15) = RSP_MEM_B(0, address++); + } +} + +template +auto RSP::LRV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto index = e; + auto start = 16 - ((address & 15) - index); + address &= ~15; + for (u32 offset = start; offset < 16; offset++) { + vt.byte(offset & 15) = RSP_MEM_B(0, address++); + } +} + +template +auto RSP::LSV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 2; + auto start = e; + auto end = std::min(start + 2, 16); + for (u32 offset = start; offset < end; offset++) { + vt.byte(offset & 15) = RSP_MEM_B(0, address++); + } +} + +template +auto RSP::LTV(u8 vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto begin = address & ~7; + address = begin + ((e + (address & 8)) & 15); + auto vtbase = vt & ~7; + auto vtoff = e >> 1; + for (u32 i = 0; i < 8; i++) { + vpu.r[vtbase + vtoff].byte(i * 2 + 0) = RSP_MEM_B(0, address++); + if (address == begin + 16) address = begin; + vpu.r[vtbase + vtoff].byte(i * 2 + 1) = RSP_MEM_B(0, address++); + if (address == begin + 16) address = begin; + vtoff = vtoff + 1 & 7; + } +} + +template +auto RSP::LUV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 8; + auto index = (address & 7) - e; + address &= ~7; + for (u32 offset = 0; offset < 8; offset++) { + vt.element(offset) = RSP_MEM_B(0, address + (index + offset & 15)) << 7; + } +} + +template +auto RSP::LWV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto start = 16 - e; + auto end = e + 16; + for (u32 offset = start; offset < end; offset++) { + vt.byte(offset & 15) = RSP_MEM_B(0, address); + address += 4; + } +} + +template +auto RSP::MFC2(r32& rt, cr128& vs) -> void { + auto hi = vs.byte(e + 0 & 15); + auto lo = vs.byte(e + 1 & 15); + rt = s16(hi << 8 | lo << 0); +} + +template +auto RSP::MTC2(cr32& rt, r128& vs) -> void { + vs.byte(e + 0) = rt >> 8; + if (e != 15) vs.byte(e + 1) = rt >> 0; +} + +template +auto RSP::SBV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm; + RSP_MEM_B(0, address) = vt.byte(e); +} + +template +auto RSP::SDV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 8; + auto start = e; + auto end = start + 8; + for (u32 offset = start; offset < end; offset++) { + RSP_MEM_B(0, address++) = vt.byte(offset & 15); + } +} + +template +auto RSP::SFV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto base = address & 7; + address &= ~7; + switch (e) { + case 0: case 15: + RSP_MEM_B(0, address + (base + 0 & 15)) = vt.element(0) >> 7; + RSP_MEM_B(0, address + (base + 4 & 15)) = vt.element(1) >> 7; + RSP_MEM_B(0, address + (base + 8 & 15)) = vt.element(2) >> 7; + RSP_MEM_B(0, address + (base + 12 & 15)) = vt.element(3) >> 7; + break; + case 1: + RSP_MEM_B(0, address + (base + 0 & 15)) = vt.element(6) >> 7; + RSP_MEM_B(0, address + (base + 4 & 15)) = vt.element(7) >> 7; + RSP_MEM_B(0, address + (base + 8 & 15)) = vt.element(4) >> 7; + RSP_MEM_B(0, address + (base + 12 & 15)) = vt.element(5) >> 7; + break; + case 4: + RSP_MEM_B(0, address + (base + 0 & 15)) = vt.element(1) >> 7; + RSP_MEM_B(0, address + (base + 4 & 15)) = vt.element(2) >> 7; + RSP_MEM_B(0, address + (base + 8 & 15)) = vt.element(3) >> 7; + RSP_MEM_B(0, address + (base + 12 & 15)) = vt.element(0) >> 7; + break; + case 5: + RSP_MEM_B(0, address + (base + 0 & 15)) = vt.element(7) >> 7; + RSP_MEM_B(0, address + (base + 4 & 15)) = vt.element(4) >> 7; + RSP_MEM_B(0, address + (base + 8 & 15)) = vt.element(5) >> 7; + RSP_MEM_B(0, address + (base + 12 & 15)) = vt.element(6) >> 7; + break; + case 8: + RSP_MEM_B(0, address + (base + 0 & 15)) = vt.element(4) >> 7; + RSP_MEM_B(0, address + (base + 4 & 15)) = vt.element(5) >> 7; + RSP_MEM_B(0, address + (base + 8 & 15)) = vt.element(6) >> 7; + RSP_MEM_B(0, address + (base + 12 & 15)) = vt.element(7) >> 7; + break; + case 11: + RSP_MEM_B(0, address + (base + 0 & 15)) = vt.element(3) >> 7; + RSP_MEM_B(0, address + (base + 4 & 15)) = vt.element(0) >> 7; + RSP_MEM_B(0, address + (base + 8 & 15)) = vt.element(1) >> 7; + RSP_MEM_B(0, address + (base + 12 & 15)) = vt.element(2) >> 7; + break; + case 12: + RSP_MEM_B(0, address + (base + 0 & 15)) = vt.element(5) >> 7; + RSP_MEM_B(0, address + (base + 4 & 15)) = vt.element(6) >> 7; + RSP_MEM_B(0, address + (base + 8 & 15)) = vt.element(7) >> 7; + RSP_MEM_B(0, address + (base + 12 & 15)) = vt.element(4) >> 7; + break; + default: + RSP_MEM_B(0, address + (base + 0 & 15)) = 0; + RSP_MEM_B(0, address + (base + 4 & 15)) = 0; + RSP_MEM_B(0, address + (base + 8 & 15)) = 0; + RSP_MEM_B(0, address + (base + 12 & 15)) = 0; + break; + } +} + +template +auto RSP::SHV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto index = address & 7; + address &= ~7; + for (u32 offset = 0; offset < 8; offset++) { + auto byte = e + offset * 2; + auto value = vt.byte(byte + 0 & 15) << 1 | vt.byte(byte + 1 & 15) >> 7; + RSP_MEM_B(0, address + (index + offset * 2 & 15)) = value; + } +} + +template +auto RSP::SLV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 4; + auto start = e; + auto end = start + 4; + for (u32 offset = start; offset < end; offset++) { + RSP_MEM_B(0, address++) = vt.byte(offset & 15); + } +} + +template +auto RSP::SPV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 8; + auto start = e; + auto end = start + 8; + for (u32 offset = start; offset < end; offset++) { + if ((offset & 15) < 8) { + RSP_MEM_B(0, address++) = vt.byte((offset & 7) << 1); + } else { + RSP_MEM_B(0, address++) = vt.element(offset & 7) >> 7; + } + } +} + +template +auto RSP::SQV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto start = e; + auto end = start + (16 - (address & 15)); + for (u32 offset = start; offset < end; offset++) { + RSP_MEM_B(0, address++) = vt.byte(offset & 15); + } +} + +template +auto RSP::SRV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto start = e; + auto end = start + (address & 15); + auto base = 16 - (address & 15); + address &= ~15; + for (u32 offset = start; offset < end; offset++) { + RSP_MEM_B(0, address++) = vt.byte(offset + base & 15); + } +} + +template +auto RSP::SSV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 2; + auto start = e; + auto end = start + 2; + for (u32 offset = start; offset < end; offset++) { + RSP_MEM_B(0, address++) = vt.byte(offset & 15); + } +} + +template +auto RSP::STV(u8 vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto start = vt & ~7; + auto end = start + 8; + auto element = 16 - (e & ~1); + auto base = (address & 7) - (e & ~1); + address &= ~7; + for (u32 offset = start; offset < end; offset++) { + RSP_MEM_B(0, address + (base++ & 15)) = vpu.r[offset].byte(element++ & 15); + RSP_MEM_B(0, address + (base++ & 15)) = vpu.r[offset].byte(element++ & 15); + } +} + +template +auto RSP::SUV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 8; + auto start = e; + auto end = start + 8; + for (u32 offset = start; offset < end; offset++) { + if ((offset & 15) < 8) { + RSP_MEM_B(0, address++) = vt.element(offset & 7) >> 7; + } else { + RSP_MEM_B(0, address++) = vt.byte((offset & 7) << 1); + } + } +} + +template +auto RSP::SWV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto start = e; + auto end = start + 16; + auto base = address & 7; + address &= ~7; + for (u32 offset = start; offset < end; offset++) { + RSP_MEM_B(0, address + (base++ & 15)) = vt.byte(offset & 15); + } +} + +template +auto RSP::VABS(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + r128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + if (vs.s16(n) < 0) { + if (vte.s16(n) == -32768) { + ACCL.s16(n) = -32768; + vd.s16(n) = 32767; + } else { + ACCL.s16(n) = -vte.s16(n); + vd.s16(n) = -vte.s16(n); + } + } else if (vs.s16(n) > 0) { + ACCL.s16(n) = +vte.s16(n); + vd.s16(n) = +vte.s16(n); + } else { + ACCL.s16(n) = 0; + vd.s16(n) = 0; + } + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vs0, slt; + vs0 = _mm_cmpeq_epi16(vs, zero); + slt = _mm_srai_epi16(vs, 15); + vd = _mm_andnot_si128(vs0, vt(e)); + vd = _mm_xor_si128(vd, slt); + ACCL = _mm_sub_epi16(vd, slt); + vd = _mm_subs_epi16(vd, slt); +#endif + } +} + +template +auto RSP::VADD(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + s32 result = vs.s16(n) + vte.s16(n) + VCOL.get(n); + ACCL.s16(n) = result; + vd.s16(n) = sclamp<16>(result); + } + VCOL = zero; + VCOH = zero; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), sum, min, max; + sum = _mm_add_epi16(vs, vte); + ACCL = _mm_sub_epi16(sum, VCOL); + min = _mm_min_epi16(vs, vte); + max = _mm_max_epi16(vs, vte); + min = _mm_subs_epi16(min, VCOL); + vd = _mm_adds_epi16(min, max); + VCOL = zero; + VCOH = zero; +#endif + } +} + +template +auto RSP::VADDC(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + u32 result = vs.u16(n) + vte.u16(n); + ACCL.u16(n) = result; + VCOL.set(n, result >> 16); + } + VCOH = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), sum; + sum = _mm_adds_epu16(vs, vte); + ACCL = _mm_add_epi16(vs, vte); + VCOL = _mm_cmpeq_epi16(sum, ACCL); + VCOL = _mm_cmpeq_epi16(VCOL, zero); + VCOH = zero; + vd = ACCL; +#endif + } +} + +template +auto RSP::VAND(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + r128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = vs.u16(n) & vte.u16(n); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_and_si128(vs, vt(e)); + vd = ACCL; +#endif + } +} + +template +auto RSP::VCH(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + if ((vs.s16(n) ^ vte.s16(n)) < 0) { + s16 result = vs.s16(n) + vte.s16(n); + ACCL.s16(n) = (result <= 0 ? -vte.s16(n) : vs.s16(n)); + VCCL.set(n, result <= 0); + VCCH.set(n, vte.s16(n) < 0); + VCOL.set(n, 1); + VCOH.set(n, result != 0 && vs.u16(n) != (vte.u16(n) ^ 0xffff)); + VCE.set(n, result == -1); + } else { + s16 result = vs.s16(n) - vte.s16(n); + ACCL.s16(n) = (result >= 0 ? vte.s16(n) : vs.s16(n)); + VCCL.set(n, vte.s16(n) < 0); + VCCH.set(n, result >= 0); + VCOL.set(n, 0); + VCOH.set(n, result != 0 && vs.u16(n) != (vte.u16(n) ^ 0xffff)); + VCE.set(n, 0); + } + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), nvt, diff, diff0, vtn, dlez, dgez, mask; + VCOL = _mm_xor_si128(vs, vte); + VCOL = _mm_cmplt_epi16(VCOL, zero); + nvt = _mm_xor_si128(vte, VCOL); + nvt = _mm_sub_epi16(nvt, VCOL); + diff = _mm_sub_epi16(vs, nvt); + diff0 = _mm_cmpeq_epi16(diff, zero); + vtn = _mm_cmplt_epi16(vte, zero); + dlez = _mm_cmpgt_epi16(diff, zero); + dgez = _mm_or_si128(dlez, diff0); + dlez = _mm_cmpeq_epi16(zero, dlez); + VCCH = _mm_blendv_epi8(dgez, vtn, VCOL); + VCCL = _mm_blendv_epi8(vtn, dlez, VCOL); + VCE = _mm_cmpeq_epi16(diff, VCOL); + VCE = _mm_and_si128(VCE, VCOL); + VCOH = _mm_or_si128(diff0, VCE); + VCOH = _mm_cmpeq_epi16(VCOH, zero); + mask = _mm_blendv_epi8(VCCH, VCCL, VCOL); + ACCL = _mm_blendv_epi8(vs, nvt, mask); + vd = ACCL; +#endif + } +} + +template +auto RSP::VCL(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + if (VCOL.get(n)) { + if (VCOH.get(n)) { + ACCL.u16(n) = VCCL.get(n) ? -vte.u16(n) : vs.u16(n); + } else { + u16 sum = vs.u16(n) + vte.u16(n); + bool carry = (vs.u16(n) + vte.u16(n)) != sum; + if (VCE.get(n)) { + ACCL.u16(n) = VCCL.set(n, (!sum || !carry)) ? -vte.u16(n) : vs.u16(n); + } else { + ACCL.u16(n) = VCCL.set(n, (!sum && !carry)) ? -vte.u16(n) : vs.u16(n); + } + } + } else { + if (VCOH.get(n)) { + ACCL.u16(n) = VCCH.get(n) ? vte.u16(n) : vs.u16(n); + } else { + ACCL.u16(n) = VCCH.set(n, (s32)vs.u16(n) - (s32)vte.u16(n) >= 0) ? vte.u16(n) : vs.u16(n); + } + } + } + VCOL = zero; + VCOH = zero; + VCE = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), nvt, diff, ncarry, nvce, diff0, lec1, lec2, leeq, geeq, le, ge, mask; + nvt = _mm_xor_si128(vte, VCOL); + nvt = _mm_sub_epi16(nvt, VCOL); + diff = _mm_sub_epi16(vs, nvt); + ncarry = _mm_adds_epu16(vs, vte); + ncarry = _mm_cmpeq_epi16(diff, ncarry); + nvce = _mm_cmpeq_epi16(VCE, zero); + diff0 = _mm_cmpeq_epi16(diff, zero); + lec1 = _mm_and_si128(diff0, ncarry); + lec1 = _mm_and_si128(nvce, lec1); + lec2 = _mm_or_si128(diff0, ncarry); + lec2 = _mm_and_si128(VCE, lec2); + leeq = _mm_or_si128(lec1, lec2); + geeq = _mm_subs_epu16(vte, vs); + geeq = _mm_cmpeq_epi16(geeq, zero); + le = _mm_andnot_si128(VCOH, VCOL); + le = _mm_blendv_epi8(VCCL, leeq, le); + ge = _mm_or_si128(VCOL, VCOH); + ge = _mm_blendv_epi8(geeq, VCCH, ge); + mask = _mm_blendv_epi8(ge, le, VCOL); + ACCL = _mm_blendv_epi8(vs, nvt, mask); + VCCH = ge; + VCCL = le; + VCOH = zero; + VCOL = zero; + VCE = zero; + vd = ACCL; +#endif + } +} + +template +auto RSP::VCR(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + if ((vs.s16(n) ^ vte.s16(n)) < 0) { + VCCH.set(n, vte.s16(n) < 0); + ACCL.u16(n) = VCCL.set(n, vs.s16(n) + vte.s16(n) + 1 <= 0) ? ~vte.u16(n) : vs.u16(n); + } else { + VCCL.set(n, vte.s16(n) < 0); + ACCL.u16(n) = VCCH.set(n, vs.s16(n) - vte.s16(n) >= 0) ? vte.u16(n) : vs.u16(n); + } + } + VCOL = zero; + VCOH = zero; + VCE = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), sign, dlez, dgez, nvt, mask; + sign = _mm_xor_si128(vs, vte); + sign = _mm_srai_epi16(sign, 15); + dlez = _mm_and_si128(vs, sign); + dlez = _mm_add_epi16(dlez, vte); + VCCL = _mm_srai_epi16(dlez, 15); + dgez = _mm_or_si128(vs, sign); + dgez = _mm_min_epi16(dgez, vte); + VCCH = _mm_cmpeq_epi16(dgez, vte); + nvt = _mm_xor_si128(vte, sign); + mask = _mm_blendv_epi8(VCCH, VCCL, sign); + ACCL = _mm_blendv_epi8(vs, nvt, mask); + vd = ACCL; + VCOL = zero; + VCOH = zero; + VCE = zero; +#endif + } +} + +template +auto RSP::VEQ(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = VCCL.set(n, !VCOH.get(n) && vs.u16(n) == vte.u16(n)) ? vs.u16(n) : vte.u16(n); + } + VCCH = zero; //unverified + VCOL = zero; + VCOH = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), eq; + eq = _mm_cmpeq_epi16(vs, vte); + VCCL = _mm_andnot_si128(VCOH, eq); + ACCL = _mm_blendv_epi8(vte, vs, VCCL); + VCCH = zero; //unverified + VCOH = zero; + VCOL = zero; + vd = ACCL; +#endif + } +} + +template +auto RSP::VGE(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = VCCL.set(n, vs.s16(n) > vte.s16(n) || (vs.s16(n) == vte.s16(n) && (!VCOL.get(n) || !VCOH.get(n)))) ? vs.u16(n) : vte.u16(n); + } + VCCH = zero; //unverified + VCOL = zero; + VCOH = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), eq, gt, es; + eq = _mm_cmpeq_epi16(vs, vte); + gt = _mm_cmpgt_epi16(vs, vte); + es = _mm_and_si128(VCOH, VCOL); + eq = _mm_andnot_si128(es, eq); + VCCL = _mm_or_si128(gt, eq); + ACCL = _mm_blendv_epi8(vte, vs, VCCL); + VCCH = zero; + VCOH = zero; + VCOL = zero; + vd = ACCL; +#endif + } +} + +template +auto RSP::VLT(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = VCCL.set(n, vs.s16(n) < vte.s16(n) || (vs.s16(n) == vte.s16(n) && VCOL.get(n) && VCOH.get(n))) ? vs.u16(n) : vte.u16(n); + } + VCCH = zero; + VCOL = zero; + VCOH = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), eq, lt; + eq = _mm_cmpeq_epi16(vs, vte); + lt = _mm_cmplt_epi16(vs, vte); + eq = _mm_and_si128(VCOH, eq); + eq = _mm_and_si128(VCOL, eq); + VCCL = _mm_or_si128(lt, eq); + ACCL = _mm_blendv_epi8(vte, vs, VCCL); + VCCH = zero; + VCOH = zero; + VCOL = zero; + vd = ACCL; +#endif + } +} + +template +auto RSP::VMACF(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, accumulatorGet(n) + (s64)vs.s16(n) * (s64)vte.s16(n) * 2); + if constexpr (U == 0) { + vd.u16(n) = accumulatorSaturate(n, 1, 0x8000, 0x7fff); + } + if constexpr (U == 1) { + vd.u16(n) = ACCH.s16(n) < 0 ? 0x0000 : ACCH.s16(n) || ACCM.s16(n) < 0 ? 0xffff : ACCM.u16(n); + } + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), lo, md, hi, carry, omask; + lo = _mm_mullo_epi16(vs, vte); + hi = _mm_mulhi_epi16(vs, vte); + md = _mm_slli_epi16(hi, 1); + carry = _mm_srli_epi16(lo, 15); + hi = _mm_srai_epi16(hi, 15); + md = _mm_or_si128(md, carry); + lo = _mm_slli_epi16(lo, 1); + omask = _mm_adds_epu16(ACCL, lo); + ACCL = _mm_add_epi16(ACCL, lo); + omask = _mm_cmpeq_epi16(ACCL, omask); + omask = _mm_cmpeq_epi16(omask, zero); + md = _mm_sub_epi16(md, omask); + carry = _mm_cmpeq_epi16(md, zero); + carry = _mm_and_si128(carry, omask); + hi = _mm_sub_epi16(hi, carry); + omask = _mm_adds_epu16(ACCM, md); + ACCM = _mm_add_epi16(ACCM, md); + omask = _mm_cmpeq_epi16(ACCM, omask); + omask = _mm_cmpeq_epi16(omask, zero); + ACCH = _mm_add_epi16(ACCH, hi); + ACCH = _mm_sub_epi16(ACCH, omask); + if constexpr (!U) { + lo = _mm_unpacklo_epi16(ACCM, ACCH); + hi = _mm_unpackhi_epi16(ACCM, ACCH); + vd = _mm_packs_epi32(lo, hi); + } else { + r128 mmask, hmask; + mmask = _mm_srai_epi16(ACCM, 15); + hmask = _mm_srai_epi16(ACCH, 15); + md = _mm_or_si128(mmask, ACCM); + omask = _mm_cmpgt_epi16(ACCH, zero); + md = _mm_andnot_si128(hmask, md); + vd = _mm_or_si128(omask, md); + } +#endif + } +} + +auto RSP::VMACQ(r128& vd) -> void { + for (u32 n = 0; n < 8; n++) { + s32 product = ACCH.element(n) << 16 | ACCM.element(n) << 0; + if (product < 0 && !(product & 1 << 5)) product += 32; + else if (product >= 32 && !(product & 1 << 5)) product -= 32; + ACCH.element(n) = product >> 16; + ACCM.element(n) = product >> 0; + vd.element(n) = sclamp<16>(product >> 1) & ~15; + } +} + +template +auto RSP::VMADH(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + s32 result = (accumulatorGet(n) >> 16) + vs.s16(n) * vte.s16(n); + ACCH.u16(n) = result >> 16; + ACCM.u16(n) = result >> 0; + vd.u16(n) = accumulatorSaturate(n, 1, 0x8000, 0x7fff); + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), lo, hi, omask; + lo = _mm_mullo_epi16(vs, vte); + hi = _mm_mulhi_epi16(vs, vte); + omask = _mm_adds_epu16(ACCM, lo); + ACCM = _mm_add_epi16(ACCM, lo); + omask = _mm_cmpeq_epi16(ACCM, omask); + omask = _mm_cmpeq_epi16(omask, zero); + hi = _mm_sub_epi16(hi, omask); + ACCH = _mm_add_epi16(ACCH, hi); + lo = _mm_unpacklo_epi16(ACCM, ACCH); + hi = _mm_unpackhi_epi16(ACCM, ACCH); + vd = _mm_packs_epi32(lo, hi); +#endif + } +} + +template +auto RSP::VMADL(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, accumulatorGet(n) + (u32(vs.u16(n) * vte.u16(n)) >> 16)); + vd.u16(n) = accumulatorSaturate(n, 0, 0x0000, 0xffff); + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), hi, omask, nhi, nmd, shi, smd, cmask, cval; + hi = _mm_mulhi_epu16(vs, vte); + omask = _mm_adds_epu16(ACCL, hi); + ACCL = _mm_add_epi16(ACCL, hi); + omask = _mm_cmpeq_epi16(ACCL, omask); + omask = _mm_cmpeq_epi16(omask, zero); + hi = _mm_sub_epi16(zero, omask); + omask = _mm_adds_epu16(ACCM, hi); + ACCM = _mm_add_epi16(ACCM, hi); + omask = _mm_cmpeq_epi16(ACCM, omask); + omask = _mm_cmpeq_epi16(omask, zero); + ACCH = _mm_sub_epi16(ACCH, omask); + nhi = _mm_srai_epi16(ACCH, 15); + nmd = _mm_srai_epi16(ACCM, 15); + shi = _mm_cmpeq_epi16(nhi, ACCH); + smd = _mm_cmpeq_epi16(nhi, nmd); + cmask = _mm_and_si128(smd, shi); + cval = _mm_cmpeq_epi16(nhi, zero); + vd = _mm_blendv_epi8(cval, ACCL, cmask); +#endif + } +} + +template +auto RSP::VMADM(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, accumulatorGet(n) + vs.s16(n) * vte.u16(n)); + vd.u16(n) = accumulatorSaturate(n, 1, 0x8000, 0x7fff); + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), lo, hi, sign, vta, omask; + lo = _mm_mullo_epi16(vs, vte); + hi = _mm_mulhi_epu16(vs, vte); + sign = _mm_srai_epi16(vs, 15); + vta = _mm_and_si128(vte, sign); + hi = _mm_sub_epi16(hi, vta); + omask = _mm_adds_epu16(ACCL, lo); + ACCL = _mm_add_epi16(ACCL, lo); + omask = _mm_cmpeq_epi16(ACCL, omask); + omask = _mm_cmpeq_epi16(omask, zero); + hi = _mm_sub_epi16(hi, omask); + omask = _mm_adds_epu16(ACCM, hi); + ACCM = _mm_add_epi16(ACCM, hi); + omask = _mm_cmpeq_epi16(ACCM, omask); + omask = _mm_cmpeq_epi16(omask, zero); + hi = _mm_srai_epi16(hi, 15); + ACCH = _mm_add_epi16(ACCH, hi); + ACCH = _mm_sub_epi16(ACCH, omask); + lo = _mm_unpacklo_epi16(ACCM, ACCH); + hi = _mm_unpackhi_epi16(ACCM, ACCH); + vd = _mm_packs_epi32(lo, hi); +#endif + } +} + +template +auto RSP::VMADN(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, accumulatorGet(n) + s64(vs.u16(n) * vte.s16(n))); + vd.u16(n) = accumulatorSaturate(n, 0, 0x0000, 0xffff); + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), lo, hi, sign, vsa, omask, nhi, nmd, shi, smd, cmask, cval; + lo = _mm_mullo_epi16(vs, vte); + hi = _mm_mulhi_epu16(vs, vte); + sign = _mm_srai_epi16(vte, 15); + vsa = _mm_and_si128(vs, sign); + hi = _mm_sub_epi16(hi, vsa); + omask = _mm_adds_epu16(ACCL, lo); + ACCL = _mm_add_epi16(ACCL, lo); + omask = _mm_cmpeq_epi16(ACCL, omask); + omask = _mm_cmpeq_epi16(omask, zero); + hi = _mm_sub_epi16(hi, omask); + omask = _mm_adds_epu16(ACCM, hi); + ACCM = _mm_add_epi16(ACCM, hi); + omask = _mm_cmpeq_epi16(ACCM, omask); + omask = _mm_cmpeq_epi16(omask, zero); + hi = _mm_srai_epi16(hi, 15); + ACCH = _mm_add_epi16(ACCH, hi); + ACCH = _mm_sub_epi16(ACCH, omask); + nhi = _mm_srai_epi16(ACCH, 15); + nmd = _mm_srai_epi16(ACCM, 15); + shi = _mm_cmpeq_epi16(nhi, ACCH); + smd = _mm_cmpeq_epi16(nhi, nmd); + cmask = _mm_and_si128(smd, shi); + cval = _mm_cmpeq_epi16(nhi, zero); + vd = _mm_blendv_epi8(cval, ACCL, cmask); +#endif + } +} + +template +auto RSP::VMOV(r128& vd, u8 de, cr128& vt) -> void { + cr128 vte = vt(e); + vd.u16(de) = vte.u16(de); + ACCL = vte; +} + +template +auto RSP::VMRG(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = VCCL.get(n) ? vs.u16(n) : vte.u16(n); + } + VCOH = zero; + VCOL = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_blendv_epi8(vt(e), vs, VCCL); + VCOH = zero; + VCOL = zero; + vd = ACCL; +#endif + } +} + +template +auto RSP::VMUDH(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, s64(vs.s16(n) * vte.s16(n)) << 16); + vd.u16(n) = accumulatorSaturate(n, 1, 0x8000, 0x7fff); + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), lo, hi; + ACCL = zero; + ACCM = _mm_mullo_epi16(vs, vte); + ACCH = _mm_mulhi_epi16(vs, vte); + lo = _mm_unpacklo_epi16(ACCM, ACCH); + hi = _mm_unpackhi_epi16(ACCM, ACCH); + vd = _mm_packs_epi32(lo, hi); +#endif + } +} + +template +auto RSP::VMUDL(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, u16(vs.u16(n) * vte.u16(n) >> 16)); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_mulhi_epu16(vs, vt(e)); + ACCM = zero; + ACCH = zero; + vd = ACCL; +#endif + } +} + +template +auto RSP::VMUDM(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, s32(vs.s16(n) * vte.u16(n))); + } + vd = ACCM; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), sign, vta; + ACCL = _mm_mullo_epi16(vs, vte); + ACCM = _mm_mulhi_epu16(vs, vte); + sign = _mm_srai_epi16(vs, 15); + vta = _mm_and_si128(vte, sign); + ACCM = _mm_sub_epi16(ACCM, vta); + ACCH = _mm_srai_epi16(ACCM, 15); + vd = ACCM; +#endif + } +} + +template +auto RSP::VMUDN(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, s32(vs.u16(n) * vte.s16(n))); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), sign, vsa; + ACCL = _mm_mullo_epi16(vs, vte); + ACCM = _mm_mulhi_epu16(vs, vte); + sign = _mm_srai_epi16(vte, 15); + vsa = _mm_and_si128(vs, sign); + ACCM = _mm_sub_epi16(ACCM, vsa); + ACCH = _mm_srai_epi16(ACCM, 15); + vd = ACCL; +#endif + } +} + +template +auto RSP::VMULF(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, (s64)vs.s16(n) * (s64)vte.s16(n) * 2 + 0x8000); + if constexpr (U == 0) { + vd.u16(n) = accumulatorSaturate(n, 1, 0x8000, 0x7fff); + } + if constexpr (U == 1) { + vd.u16(n) = ACCH.s16(n) < 0 ? 0x0000 : (ACCH.s16(n) ^ ACCM.s16(n)) < 0 ? 0xffff : ACCM.u16(n); + } + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), lo, hi, round, sign1, sign2, neq, eq, neg; + lo = _mm_mullo_epi16(vs, vte); + round = _mm_cmpeq_epi16(zero, zero); + sign1 = _mm_srli_epi16(lo, 15); + lo = _mm_add_epi16(lo, lo); + round = _mm_slli_epi16(round, 15); + hi = _mm_mulhi_epi16(vs, vte); + sign2 = _mm_srli_epi16(lo, 15); + ACCL = _mm_add_epi16(round, lo); + sign1 = _mm_add_epi16(sign1, sign2); + hi = _mm_slli_epi16(hi, 1); + neq = _mm_cmpeq_epi16(vs, vte); + ACCM = _mm_add_epi16(hi, sign1); + neg = _mm_srai_epi16(ACCM, 15); + if constexpr (!U) { + eq = _mm_and_si128(neq, neg); + ACCH = _mm_andnot_si128(neq, neg); + vd = _mm_add_epi16(ACCM, eq); + } else { + ACCH = _mm_andnot_si128(neq, neg); + hi = _mm_or_si128(ACCM, neg); + vd = _mm_andnot_si128(ACCH, hi); + } +#endif + } +} + +template +auto RSP::VMULQ(r128& vd, cr128& vs, cr128& vt) -> void { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + s32 product = (s16)vs.element(n) * (s16)vte.element(n); + if (product < 0) product += 31; //round + ACCH.element(n) = product >> 16; + ACCM.element(n) = product >> 0; + ACCL.element(n) = 0; + vd.element(n) = sclamp<16>(product >> 1) & ~15; + } +} + +template +auto RSP::VNAND(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = ~(vs.u16(n) & vte.u16(n)); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_and_si128(vs, vt(e)); + ACCL = _mm_xor_si128(ACCL, invert); + vd = ACCL; +#endif + } +} + +template +auto RSP::VNE(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = VCCL.set(n, vs.u16(n) != vte.u16(n) || VCOH.get(n)) ? vs.u16(n) : vte.u16(n); + } + VCCH = zero; //unverified + VCOL = zero; + VCOH = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), eq, ne; + eq = _mm_cmpeq_epi16(vs, vte); + ne = _mm_cmpeq_epi16(eq, zero); + VCCL = _mm_and_si128(VCOH, eq); + VCCL = _mm_or_si128(VCCL, ne); + ACCL = _mm_blendv_epi8(vte, vs, VCCL); + VCCH = zero; + VCOH = zero; + VCOL = zero; + vd = ACCL; +#endif + } +} + +auto RSP::VNOP() -> void { +} + +template +auto RSP::VNOR(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = ~(vs.u16(n) | vte.u16(n)); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_or_si128(vs, vt(e)); + ACCL = _mm_xor_si128(ACCL, invert); + vd = ACCL; +#endif + } +} + +template +auto RSP::VNXOR(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = ~(vs.u16(n) ^ vte.u16(n)); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_xor_si128(vs, vt(e)); + ACCL = _mm_xor_si128(ACCL, invert); + vd = ACCL; +#endif + } +} + +template +auto RSP::VOR(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = vs.u16(n) | vte.u16(n); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_or_si128(vs, vt(e)); + vd = ACCL; +#endif + } +} + +template +auto RSP::VRCP(r128& vd, u8 de, cr128& vt) -> void { + s32 result = 0; + s32 input = L && DIVDP ? DIVIN << 16 | vt.element(e & 7) : s16(vt.element(e & 7)); + s32 mask = input >> 31; + s32 data = input ^ mask; + if (input > -32768) data -= mask; + if (data == 0) { + result = 0x7fff'ffff; + } else if (input == -32768) { + result = 0xffff'0000; + } else { + u32 shift = __builtin_clz(data); + u32 index = (u64(data) << shift & 0x7fc0'0000) >> 22; + result = rspReciprocals[index]; + result = (0x10000 | result) << 14; + result = result >> 31 - shift ^ mask; + } + DIVDP = 0; + DIVOUT = result >> 16; + ACCL = vt(e); + vd.element(de) = result; +} + +template +auto RSP::VRCPH(r128& vd, u8 de, cr128& vt) -> void { + ACCL = vt(e); + DIVDP = 1; + DIVIN = vt.element(e & 7); + vd.element(de) = DIVOUT; +} + +template +auto RSP::VRND(r128& vd, u8 vs, cr128& vt) -> void { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + s32 product = (s16)vte.element(n); + if (vs & 1) product <<= 16; + s64 acc = 0; + acc |= ACCH.element(n); acc <<= 16; + acc |= ACCM.element(n); acc <<= 16; + acc |= ACCL.element(n); acc <<= 16; + acc >>= 16; + if (D == 0 && acc < 0) acc = sclip<48>(acc + product); + if (D == 1 && acc >= 0) acc = sclip<48>(acc + product); + ACCH.element(n) = acc >> 32; + ACCM.element(n) = acc >> 16; + ACCL.element(n) = acc >> 0; + vd.element(n) = sclamp<16>(acc >> 16); + } +} + +template +auto RSP::VRSQ(r128& vd, u8 de, cr128& vt) -> void { + s32 result = 0; + s32 input = L && DIVDP ? DIVIN << 16 | vt.element(e & 7) : s16(vt.element(e & 7)); + s32 mask = input >> 31; + s32 data = input ^ mask; + if (input > -32768) data -= mask; + if (data == 0) { + result = 0x7fff'ffff; + } else if (input == -32768) { + result = 0xffff'0000; + } else { + u32 shift = __builtin_clz(data); + u32 index = (u64(data) << shift & 0x7fc0'0000) >> 22; + result = rspInverseSquareRoots[index & 0x1fe | shift & 1]; + result = (0x10000 | result) << 14; + result = result >> (31 - shift >> 1) ^ mask; + } + DIVDP = 0; + DIVOUT = result >> 16; + ACCL = vt(e); + vd.element(de) = result; +} + +template +auto RSP::VRSQH(r128& vd, u8 de, cr128& vt) -> void { + ACCL = vt(e); + DIVDP = 1; + DIVIN = vt.element(e & 7); + vd.element(de) = DIVOUT; +} + +template +auto RSP::VSAR(r128& vd, cr128& vs) -> void { + switch (e) { + case 0x8: vd = ACCH; break; + case 0x9: vd = ACCM; break; + case 0xa: vd = ACCL; break; + default: vd = zero; break; + } +} + +template +auto RSP::VSUB(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + s32 result = vs.s16(n) - vte.s16(n) - VCOL.get(n); + ACCL.s16(n) = result; + vd.s16(n) = sclamp<16>(result); + } + VCOL = zero; + VCOH = zero; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), udiff, sdiff, ov; + udiff = _mm_sub_epi16(vte, VCOL); + sdiff = _mm_subs_epi16(vte, VCOL); + ACCL = _mm_sub_epi16(vs, udiff); + ov = _mm_cmpgt_epi16(sdiff, udiff); + vd = _mm_subs_epi16(vs, sdiff); + vd = _mm_adds_epi16(vd, ov); + VCOL = zero; + VCOH = zero; +#endif + } +} + +template +auto RSP::VSUBC(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + u32 result = vs.u16(n) - vte.u16(n); + ACCL.u16(n) = result; + VCOL.set(n, result >> 16); + VCOH.set(n, result != 0); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), equal, udiff, diff0; + udiff = _mm_subs_epu16(vs, vte); + equal = _mm_cmpeq_epi16(vs, vte); + diff0 = _mm_cmpeq_epi16(udiff, zero); + VCOH = _mm_cmpeq_epi16(equal, zero); + VCOL = _mm_andnot_si128(equal, diff0); + ACCL = _mm_sub_epi16(vs, vte); + vd = ACCL; +#endif + } +} + +template +auto RSP::VXOR(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = vs.u16(n) ^ vte.u16(n); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_xor_si128(vs, vt(e)); + vd = ACCL; +#endif + } +} + +template +auto RSP::VZERO(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + s32 result = vs.s16(n) + vte.s16(n); + ACCL.s16(n) = result; + vd.s16(n) = 0; + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), sum, min, max; + ACCL = _mm_add_epi16(vs, vte); + vd = _mm_xor_si128(vd, vd); +#endif + } +} + +#undef ACCH +#undef ACCM +#undef ACCL +#undef VCOH +#undef VCOL +#undef VCCH +#undef VCCL +#undef VCE + +#undef DIVIN +#undef DIVOUT +#undef DIVDP diff --git a/include/rt64_layer.h b/include/rt64_layer.h new file mode 100644 index 0000000..b60d667 --- /dev/null +++ b/include/rt64_layer.h @@ -0,0 +1,61 @@ +#ifndef __RT64_LAYER_H__ +#define __RT64_LAYER_H__ + +typedef struct { + unsigned char* HEADER; /* This is the rom header (first 40h bytes of the rom) */ + unsigned char* RDRAM; + unsigned char* DMEM; + unsigned char* IMEM; + + unsigned int* MI_INTR_REG; + + unsigned int* DPC_START_REG; + unsigned int* DPC_END_REG; + unsigned int* DPC_CURRENT_REG; + unsigned int* DPC_STATUS_REG; + unsigned int* DPC_CLOCK_REG; + unsigned int* DPC_BUFBUSY_REG; + unsigned int* DPC_PIPEBUSY_REG; + unsigned int* DPC_TMEM_REG; + + unsigned int* VI_STATUS_REG; + unsigned int* VI_ORIGIN_REG; + unsigned int* VI_WIDTH_REG; + unsigned int* VI_INTR_REG; + unsigned int* VI_V_CURRENT_LINE_REG; + unsigned int* VI_TIMING_REG; + unsigned int* VI_V_SYNC_REG; + unsigned int* VI_H_SYNC_REG; + unsigned int* VI_LEAP_REG; + unsigned int* VI_H_START_REG; + unsigned int* VI_V_START_REG; + unsigned int* VI_V_BURST_REG; + unsigned int* VI_X_SCALE_REG; + unsigned int* VI_Y_SCALE_REG; + + void (*CheckInterrupts)(void); + + unsigned int version; + unsigned int* SP_STATUS_REG; + const unsigned int* RDRAM_SIZE; +} GFX_INFO; + +#define DLLEXPORT extern "C" __declspec(dllexport) +#define DLLIMPORT extern "C" __declspec(dllimport) +#define CALL __cdecl + +// Dynamic loading +//DLLEXPORT int (CALL *InitiateGFX)(GFX_INFO Gfx_Info) = nullptr; +//DLLEXPORT void (CALL *ProcessRDPList)(void) = nullptr; +//DLLEXPORT void (CALL *ProcessDList)(void) = nullptr; +//DLLEXPORT void (CALL *UpdateScreen)(void) = nullptr; +//DLLEXPORT void (CALL *PumpEvents)(void) = nullptr; + +DLLIMPORT int InitiateGFX(GFX_INFO Gfx_Info); +DLLIMPORT void ProcessRDPList(void); +DLLIMPORT void ProcessDList(void); +DLLIMPORT void UpdateScreen(void); +DLLIMPORT void PumpEvents(void); + +#endif + diff --git a/include/sections.h b/include/sections.h new file mode 100644 index 0000000..1d1b228 --- /dev/null +++ b/include/sections.h @@ -0,0 +1,23 @@ +#ifndef __SECTIONS_H__ +#define __SECTIONS_H__ + +#include +#include "recomp.h" + +#define ARRLEN(x) (sizeof(x) / sizeof((x)[0])) + +typedef struct { + recomp_func_t* func; + uint32_t offset; +} FuncEntry; + +typedef struct { + uint32_t rom_addr; + uint32_t ram_addr; + uint32_t size; + FuncEntry *funcs; + size_t num_funcs; + size_t index; +} SectionTableEntry; + +#endif diff --git a/portultra/audio.cpp b/portultra/audio.cpp new file mode 100644 index 0000000..ccd35c4 --- /dev/null +++ b/portultra/audio.cpp @@ -0,0 +1,88 @@ +#include "ultra64.h" +#include "multilibultra.hpp" +#include "SDL.h" +#include "SDL_audio.h" +#include + +static SDL_AudioDeviceID audio_device = 0; +static uint32_t sample_rate = 48000; + +void Multilibultra::init_audio() { + // Initialize SDL audio. + SDL_InitSubSystem(SDL_INIT_AUDIO); + // Pick an initial dummy sample rate; this will be set by the game later to the true sample rate. + set_audio_frequency(48000); +} + +void Multilibultra::set_audio_frequency(uint32_t freq) { + if (audio_device != 0) { + SDL_CloseAudioDevice(audio_device); + } + SDL_AudioSpec spec_desired{ + .freq = (int)freq, + .format = AUDIO_S16, + .channels = 2, + .silence = 0, // calculated + .samples = 0x100, // Fairly small sample count to reduce the latency of internal buffering + .padding = 0, // unused + .size = 0, // calculated + .callback = nullptr,//feed_audio, // Use a callback as QueueAudio causes popping + .userdata = nullptr + }; + + audio_device = SDL_OpenAudioDevice(nullptr, false, &spec_desired, nullptr, 0); + if (audio_device == 0) { + printf("SDL Error: %s\n", SDL_GetError()); + fflush(stdout); + assert(false); + } + SDL_PauseAudioDevice(audio_device, 0); + sample_rate = freq; +} + +void Multilibultra::queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data_, uint32_t byte_count) { + // Buffer for holding the output of swapping the audio channels. This is reused across + // calls to reduce runtime allocations. + static std::vector swap_buffer; + + // Ensure that the byte count is an integer multiple of samples. + assert((byte_count & 1) == 0); + + // Calculate the number of samples from the number of bytes. + uint32_t sample_count = byte_count / sizeof(s16); + + // Make sure the swap buffer is large enough to hold all the incoming audio data. + if (sample_count > swap_buffer.size()) { + swap_buffer.resize(sample_count); + } + + // Swap the audio channels into the swap buffer to correct for the address xor caused by endianness handling. + s16* audio_data = TO_PTR(s16, audio_data_); + for (size_t i = 0; i < sample_count; i += 2) { + swap_buffer[i + 0] = audio_data[i + 1]; + swap_buffer[i + 1] = audio_data[i + 0]; + } + + // Queue the swapped audio data. + SDL_QueueAudio(audio_device, swap_buffer.data(), byte_count); +} + +// If there's ever any audio popping, check here first. Some games are very sensitive to +// the remaining sample count and reporting a number that's too high here can lead to issues. +// Reporting a number that's too low can lead to audio lag in some games. +uint32_t Multilibultra::get_remaining_audio_bytes() { + // Get the number of remaining buffered audio bytes. + uint32_t buffered_byte_count = SDL_GetQueuedAudioSize(audio_device); + + // Adjust the reported count to be four refreshes in the future, which helps ensure that + // there are enough samples even if the game experiences a small amount of lag. This prevents + // audio popping on games that use the buffered audio byte count to determine how many samples + // to generate. + uint32_t samples_per_vi = (sample_rate / 60); + if (buffered_byte_count > (4u * samples_per_vi)) { + buffered_byte_count -= (4u * samples_per_vi); + } else { + buffered_byte_count = 0; + } + return buffered_byte_count; +} diff --git a/portultra/events.cpp b/portultra/events.cpp new file mode 100644 index 0000000..be30b8c --- /dev/null +++ b/portultra/events.cpp @@ -0,0 +1,340 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "SDL.h" +#include "blockingconcurrentqueue.h" + +#include "ultra64.h" +#include "multilibultra.hpp" +#include "recomp.h" +#include "rsp.h" + +struct SpTaskAction { + OSTask task; +}; + +struct SwapBuffersAction { + uint32_t origin; +}; + +using Action = std::variant; + +static struct { + struct { + std::thread thread; + PTR(OSMesgQueue) mq = NULLPTR; + PTR(void) current_buffer = NULLPTR; + PTR(void) next_buffer = NULLPTR; + OSMesg msg = (OSMesg)0; + int retrace_count = 1; + } vi; + struct { + std::thread thread; + PTR(OSMesgQueue) mq = NULLPTR; + OSMesg msg = (OSMesg)0; + } sp; + struct { + std::thread thread; + PTR(OSMesgQueue) mq = NULLPTR; + OSMesg msg = (OSMesg)0; + } dp; + struct { + std::thread thread; + PTR(OSMesgQueue) mq = NULLPTR; + OSMesg msg = (OSMesg)0; + } ai; + struct { + std::thread thread; + PTR(OSMesgQueue) mq = NULLPTR; + OSMesg msg = (OSMesg)0; + } si; + // The same message queue may be used for multiple events, so share a mutex for all of them + std::mutex message_mutex; + uint8_t* rdram; + moodycamel::BlockingConcurrentQueue action_queue{}; +} events_context{}; + +extern "C" void osSetEventMesg(RDRAM_ARG OSEvent event_id, PTR(OSMesgQueue) mq_, OSMesg msg) { + OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_); + std::lock_guard lock{ events_context.message_mutex }; + + switch (event_id) { + case OS_EVENT_SP: + events_context.sp.msg = msg; + events_context.sp.mq = mq_; + break; + case OS_EVENT_DP: + events_context.dp.msg = msg; + events_context.dp.mq = mq_; + break; + case OS_EVENT_AI: + events_context.ai.msg = msg; + events_context.ai.mq = mq_; + break; + case OS_EVENT_SI: + events_context.si.msg = msg; + events_context.si.mq = mq_; + } +} + +extern "C" void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, u32 retrace_count) { + std::lock_guard lock{ events_context.message_mutex }; + events_context.vi.mq = mq_; + events_context.vi.msg = msg; + events_context.vi.retrace_count = retrace_count; +} + +void vi_thread_func() { + using namespace std::chrono_literals; + + uint64_t total_vis = 0; + int remaining_retraces = events_context.vi.retrace_count; + + while (true) { + // Determine the next VI time (more accurate than adding 16ms each VI interrupt) + auto next = Multilibultra::get_start() + (total_vis * 1000000us) / (60 * Multilibultra::get_speed_multiplier()); + //if (next > std::chrono::system_clock::now()) { + // printf("Sleeping for %" PRIu64 " us to get from %" PRIu64 " us to %" PRIu64 " us \n", + // (next - std::chrono::system_clock::now()) / 1us, + // (std::chrono::system_clock::now() - events_context.start) / 1us, + // (next - events_context.start) / 1us); + //} else { + // printf("No need to sleep\n"); + //} + std::this_thread::sleep_until(next); + // Calculate how many VIs have passed + uint64_t new_total_vis = (Multilibultra::time_since_start() * (60 * Multilibultra::get_speed_multiplier()) / 1000ms) + 1; + if (new_total_vis > total_vis + 1) { + //printf("Skipped % " PRId64 " frames in VI interupt thread!\n", new_total_vis - total_vis - 1); + } + total_vis = new_total_vis; + + remaining_retraces--; + + { + std::lock_guard lock{ events_context.message_mutex }; + uint8_t* rdram = events_context.rdram; + if (remaining_retraces == 0) { + remaining_retraces = events_context.vi.retrace_count; + + if (events_context.vi.mq != NULLPTR) { + if (osSendMesg(PASS_RDRAM events_context.vi.mq, events_context.vi.msg, OS_MESG_NOBLOCK) == -1) { + //printf("Game skipped a VI frame!\n"); + } + } + } + if (events_context.ai.mq != NULLPTR) { + if (osSendMesg(PASS_RDRAM events_context.ai.mq, events_context.ai.msg, OS_MESG_NOBLOCK) == -1) { + //printf("Game skipped a AI frame!\n"); + } + } + } + } +} + +void sp_complete() { + uint8_t* rdram = events_context.rdram; + std::lock_guard lock{ events_context.message_mutex }; + osSendMesg(PASS_RDRAM events_context.sp.mq, events_context.sp.msg, OS_MESG_NOBLOCK); +} + +void dp_complete() { + uint8_t* rdram = events_context.rdram; + std::lock_guard lock{ events_context.message_mutex }; + osSendMesg(PASS_RDRAM events_context.dp.mq, events_context.dp.msg, OS_MESG_NOBLOCK); +} + +void RT64Init(uint8_t* rom, uint8_t* rdram); +void RT64SendDL(uint8_t* rdram, const OSTask* task); +void RT64UpdateScreen(uint32_t vi_origin); + +std::unordered_map button_map{ + { SDL_Scancode::SDL_SCANCODE_LEFT, 0x0002 }, // c left + { SDL_Scancode::SDL_SCANCODE_RIGHT, 0x0001 }, // c right + { SDL_Scancode::SDL_SCANCODE_UP, 0x0008 }, // c up + { SDL_Scancode::SDL_SCANCODE_DOWN, 0x0004 }, // c down + { SDL_Scancode::SDL_SCANCODE_RETURN, 0x1000 }, // start + { SDL_Scancode::SDL_SCANCODE_SPACE, 0x8000 }, // a + { SDL_Scancode::SDL_SCANCODE_LSHIFT, 0x4000 }, // b + { SDL_Scancode::SDL_SCANCODE_Q, 0x2000 }, // z + { SDL_Scancode::SDL_SCANCODE_E, 0x0020 }, // l + { SDL_Scancode::SDL_SCANCODE_R, 0x0010 }, // r + { SDL_Scancode::SDL_SCANCODE_J, 0x0200 }, // dpad left + { SDL_Scancode::SDL_SCANCODE_L, 0x0100 }, // dpad right + { SDL_Scancode::SDL_SCANCODE_I, 0x0800 }, // dpad up + { SDL_Scancode::SDL_SCANCODE_K, 0x0400 }, // dpad down +}; + +extern int button; +extern int stick_x; +extern int stick_y; + +int sdl_event_filter(void* userdata, SDL_Event* event) { + switch (event->type) { + case SDL_EventType::SDL_KEYUP: + case SDL_EventType::SDL_KEYDOWN: + { + const Uint8* key_states = SDL_GetKeyboardState(nullptr); + int new_button = 0; + + for (const auto& mapping : button_map) { + if (key_states[mapping.first]) { + new_button |= mapping.second; + } + } + + button = new_button; + + stick_x = 127 * (key_states[SDL_Scancode::SDL_SCANCODE_D] - key_states[SDL_Scancode::SDL_SCANCODE_A]); + stick_y = 127 * (key_states[SDL_Scancode::SDL_SCANCODE_W] - key_states[SDL_Scancode::SDL_SCANCODE_S]); + } + break; + case SDL_EventType::SDL_QUIT: + std::quick_exit(ERROR_SUCCESS); + break; + } + return 1; +} + +uint8_t dmem[0x1000]; +uint16_t rspReciprocals[512]; +uint16_t rspInverseSquareRoots[512]; + +using RspUcodeFunc = RspExitReason(uint8_t* rdram); +extern RspUcodeFunc njpgdspMain; +extern RspUcodeFunc aspMain; + +// From Ares emulator. For license details, see rsp_vu.h +void rsp_constants_init() { + rspReciprocals[0] = u16(~0); + for (u16 index = 1; index < 512; index++) { + u64 a = index + 512; + u64 b = (u64(1) << 34) / a; + rspReciprocals[index] = u16(b + 1 >> 8); + } + + for (u16 index = 0; index < 512; index++) { + u64 a = index + 512 >> ((index % 2 == 1) ? 1 : 0); + u64 b = 1 << 17; + //find the largest b where b < 1.0 / sqrt(a) + while (a * (b + 1) * (b + 1) < (u64(1) << 44)) b++; + rspInverseSquareRoots[index] = u16(b >> 1); + } +} + +// Runs a recompiled RSP microcode +void run_rsp_microcode(uint8_t* rdram, const OSTask* task, RspUcodeFunc* ucode_func) { + // Load the OSTask into DMEM + memcpy(&dmem[0xFC0], task, sizeof(OSTask)); + // Load the ucode data into DMEM + dma_rdram_to_dmem(rdram, 0x0000, task->t.ucode_data, 0xF80 - 1); + // Run the ucode + RspExitReason exit_reason = ucode_func(rdram); + // Ensure that the ucode exited correctly + assert(exit_reason == RspExitReason::Broke); + sp_complete(); +} + +void event_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* events_thread_ready) { + using namespace std::chrono_literals; + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { + fprintf(stderr, "Failed to initialize SDL2: %s\n", SDL_GetError()); + std::quick_exit(EXIT_FAILURE); + } + RT64Init(rom, rdram); + SDL_Window* window = SDL_GetWindowFromID(1); + // TODO set this window title in RT64, create the window here and send it to RT64, or something else entirely + // as the current window name visibly changes as RT64 is initialized + SDL_SetWindowTitle(window, "Recomp"); + //SDL_SetEventFilter(sdl_event_filter, nullptr); + + rsp_constants_init(); + + // Notify the caller thread that this thread is ready. + events_thread_ready->test_and_set(); + events_thread_ready->notify_all(); + + while (true) { + // Try to pull an action from the queue + Action action; + if (events_context.action_queue.wait_dequeue_timed(action, 1ms)) { + // Determine the action type and act on it + if (const auto* task_action = std::get_if(&action)) { + if (task_action->task.t.type == M_GFXTASK) { + // (TODO let RT64 do this) Tell the game that the RSP and RDP tasks are complete + RT64SendDL(rdram, &task_action->task); + sp_complete(); + dp_complete(); + } else if (task_action->task.t.type == M_AUDTASK) { + run_rsp_microcode(rdram, &task_action->task, aspMain); + } else if (task_action->task.t.type == M_NJPEGTASK) { + run_rsp_microcode(rdram, &task_action->task, njpgdspMain); + } else { + fprintf(stderr, "Unknown task type: %" PRIu32 "\n", task_action->task.t.type); + assert(false); + std::quick_exit(EXIT_FAILURE); + } + } else if (const auto* swap_action = std::get_if(&action)) { + static volatile int i = 0; + if (i >= 100) { + i = 0; + } + i++; + events_context.vi.current_buffer = events_context.vi.next_buffer; + RT64UpdateScreen(swap_action->origin); + } + } + + // Handle events + constexpr int max_events_per_frame = 16; + SDL_Event cur_event; + int i = 0; + while (i++ < max_events_per_frame && SDL_PollEvent(&cur_event)) { + sdl_event_filter(nullptr, &cur_event); + } + //SDL_PumpEvents(); + } +} + +extern "C" void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr) { + events_context.vi.next_buffer = frameBufPtr; + events_context.action_queue.enqueue(SwapBuffersAction{ osVirtualToPhysical(frameBufPtr) + 640 }); +} + +extern "C" PTR(void) osViGetNextFramebuffer() { + return events_context.vi.next_buffer; +} + +extern "C" PTR(void) osViGetCurrentFramebuffer() { + return events_context.vi.current_buffer; +} + +void Multilibultra::submit_rsp_task(RDRAM_ARG PTR(OSTask) task_) { + OSTask* task = TO_PTR(OSTask, task_); + events_context.action_queue.enqueue(SpTaskAction{ *task }); +} + +void Multilibultra::send_si_message() { + uint8_t* rdram = events_context.rdram; + osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK); +} + +void Multilibultra::init_events(uint8_t* rdram, uint8_t* rom) { + std::atomic_flag events_thread_ready; + events_context.rdram = rdram; + events_context.sp.thread = std::thread{ event_thread_func, rdram, rom, &events_thread_ready }; + + // Wait for the event thread to be ready before continuing to prevent the game from + // running before we're able to handle RSP tasks. + events_thread_ready.wait(false); + + events_context.vi.thread = std::thread{ vi_thread_func }; +} diff --git a/portultra/mesgqueue.cpp b/portultra/mesgqueue.cpp new file mode 100644 index 0000000..fd7a0bb --- /dev/null +++ b/portultra/mesgqueue.cpp @@ -0,0 +1,194 @@ +#include +#include + +#include "ultra64.h" +#include "multilibultra.hpp" +#include "recomp.h" + +extern "C" void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg, s32 count) { + OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_); + mq->blocked_on_recv = NULLPTR; + mq->blocked_on_send = NULLPTR; + mq->msgCount = count; + mq->msg = msg; + mq->validCount = 0; + mq->first = 0; +} + +s32 MQ_GET_COUNT(OSMesgQueue *mq) { + return mq->validCount; +} + +s32 MQ_IS_EMPTY(OSMesgQueue *mq) { + return mq->validCount == 0; +} + +s32 MQ_IS_FULL(OSMesgQueue* mq) { + return MQ_GET_COUNT(mq) >= mq->msgCount; +} + +void thread_queue_insert(RDRAM_ARG PTR(OSThread)* queue, PTR(OSThread) toadd_) { + PTR(OSThread)* cur = queue; + OSThread* toadd = TO_PTR(OSThread, toadd_); + while (*cur && TO_PTR(OSThread, *cur)->priority > toadd->priority) { + cur = &TO_PTR(OSThread, *cur)->next; + } + toadd->next = (*cur); + *cur = toadd_; +} + +OSThread* thread_queue_pop(RDRAM_ARG PTR(OSThread)* queue) { + PTR(OSThread) ret = *queue; + *queue = TO_PTR(OSThread, ret)->next; + return TO_PTR(OSThread, ret); +} + +bool thread_queue_empty(RDRAM_ARG PTR(OSThread)* queue) { + return *queue == NULLPTR; +} + +extern "C" s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) { + OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_); + + // Prevent accidentally blocking anything that isn't a game thread + if (!Multilibultra::is_game_thread()) { + flags = OS_MESG_NOBLOCK; + } + + Multilibultra::disable_preemption(); + + if (flags == OS_MESG_NOBLOCK) { + // If non-blocking, fail if the queue is full + if (MQ_IS_FULL(mq)) { + Multilibultra::enable_preemption(); + return -1; + } + } else { + // Otherwise, yield this thread until the queue has room + while (MQ_IS_FULL(mq)) { + debug_printf("[Message Queue] Thread %d is blocked on send\n", TO_PTR(OSThread, Multilibultra::this_thread())->id); + thread_queue_insert(PASS_RDRAM &mq->blocked_on_send, Multilibultra::this_thread()); + Multilibultra::enable_preemption(); + Multilibultra::pause_self(PASS_RDRAM1); + Multilibultra::disable_preemption(); + } + } + + s32 last = (mq->first + mq->validCount) % mq->msgCount; + TO_PTR(OSMesg, mq->msg)[last] = msg; + mq->validCount++; + + OSThread* to_run = nullptr; + + if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_recv)) { + to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_recv); + } + + Multilibultra::enable_preemption(); + if (to_run) { + debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id); + if (Multilibultra::is_game_thread()) { + OSThread* self = TO_PTR(OSThread, Multilibultra::this_thread()); + if (to_run->priority > self->priority) { + Multilibultra::swap_to_thread(PASS_RDRAM to_run); + } else { + Multilibultra::schedule_running_thread(to_run); + } + } else { + Multilibultra::schedule_running_thread(to_run); + } + } + return 0; +} + +extern "C" s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) { + OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_); + Multilibultra::disable_preemption(); + + if (flags == OS_MESG_NOBLOCK) { + // If non-blocking, fail if the queue is full + if (MQ_IS_FULL(mq)) { + Multilibultra::enable_preemption(); + return -1; + } + } else { + // Otherwise, yield this thread in a loop until the queue is no longer full + while (MQ_IS_FULL(mq)) { + debug_printf("[Message Queue] Thread %d is blocked on jam\n", TO_PTR(OSThread, Multilibultra::this_thread())->id); + thread_queue_insert(PASS_RDRAM &mq->blocked_on_send, Multilibultra::this_thread()); + Multilibultra::enable_preemption(); + Multilibultra::pause_self(PASS_RDRAM1); + Multilibultra::disable_preemption(); + } + } + + mq->first = (mq->first + mq->msgCount - 1) % mq->msgCount; + TO_PTR(OSMesg, mq->msg)[mq->first] = msg; + mq->validCount++; + + OSThread *to_run = nullptr; + + if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_recv)) { + to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_recv); + } + + Multilibultra::enable_preemption(); + if (to_run) { + debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id); + OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread()); + if (to_run->priority > self->priority) { + Multilibultra::swap_to_thread(PASS_RDRAM to_run); + } else { + Multilibultra::schedule_running_thread(to_run); + } + } + return 0; +} + +extern "C" s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg_, s32 flags) { + OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_); + OSMesg *msg = TO_PTR(OSMesg, msg_); + Multilibultra::disable_preemption(); + + if (flags == OS_MESG_NOBLOCK) { + // If non-blocking, fail if the queue is empty + if (MQ_IS_EMPTY(mq)) { + Multilibultra::enable_preemption(); + return -1; + } + } else { + // Otherwise, yield this thread in a loop until the queue is no longer full + while (MQ_IS_EMPTY(mq)) { + debug_printf("[Message Queue] Thread %d is blocked on receive\n", TO_PTR(OSThread, Multilibultra::this_thread())->id); + thread_queue_insert(PASS_RDRAM &mq->blocked_on_recv, Multilibultra::this_thread()); + Multilibultra::enable_preemption(); + Multilibultra::pause_self(PASS_RDRAM1); + Multilibultra::disable_preemption(); + } + } + + if (msg_ != NULLPTR) { + *msg = TO_PTR(OSMesg, mq->msg)[mq->first]; + } + + mq->first = (mq->first + 1) % mq->msgCount; + mq->validCount--; + + OSThread *to_run = nullptr; + + if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_send)) { + to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_send); + } + + Multilibultra::enable_preemption(); + if (to_run) { + debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id); + OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread()); + if (to_run->priority > self->priority) { + Multilibultra::swap_to_thread(PASS_RDRAM to_run); + } else { + Multilibultra::schedule_running_thread(to_run); + } + } + return 0; +} diff --git a/portultra/misc_ultra.cpp b/portultra/misc_ultra.cpp new file mode 100644 index 0000000..2b794b7 --- /dev/null +++ b/portultra/misc_ultra.cpp @@ -0,0 +1,22 @@ +#include "ultra64.h" + +#define K0BASE 0x80000000 +#define K1BASE 0xA0000000 +#define K2BASE 0xC0000000 +#define IS_KSEG0(x) ((u32)(x) >= K0BASE && (u32)(x) < K1BASE) +#define IS_KSEG1(x) ((u32)(x) >= K1BASE && (u32)(x) < K2BASE) +#define K0_TO_PHYS(x) ((u32)(x)&0x1FFFFFFF) /* kseg0 to physical */ +#define K1_TO_PHYS(x) ((u32)(x)&0x1FFFFFFF) /* kseg1 to physical */ + +u32 osVirtualToPhysical(PTR(void) addr) { + uintptr_t addr_val = (uintptr_t)addr; + if (IS_KSEG0(addr_val)) { + return K0_TO_PHYS(addr_val); + } else if (IS_KSEG1(addr_val)) { + return K1_TO_PHYS(addr_val); + } else { + // TODO handle TLB mappings + return (u32)addr_val; + } +} + diff --git a/portultra/multilibultra.hpp b/portultra/multilibultra.hpp new file mode 100644 index 0000000..c1470c2 --- /dev/null +++ b/portultra/multilibultra.hpp @@ -0,0 +1,75 @@ +#ifndef __MULTILIBULTRA_HPP__ +#define __MULTILIBULTRA_HPP__ + +#include +#include +#include +#include + +#include "ultra64.h" +#include "platform_specific.h" + +struct UltraThreadContext { + std::thread host_thread; + std::atomic_bool running; + std::atomic_bool initialized; +}; + +namespace Multilibultra { + +// We need a place in rdram to hold the PI handles, so pick an address in extended rdram +constexpr int32_t cart_handle = 0x80800000; +constexpr int32_t flash_handle = (int32_t)(cart_handle + sizeof(OSPiHandle)); +constexpr uint32_t save_size = 1024 * 1024 / 8; // Maximum save size, 1Mbit for flash + +void preinit(uint8_t* rdram, uint8_t* rom); +void save_init(); +void native_init(); +void init_scheduler(); +void init_events(uint8_t* rdram, uint8_t* rom); +void init_timers(RDRAM_ARG1); +void native_thread_init(OSThread *t); +void set_self_paused(RDRAM_ARG1); +void wait_for_resumed(RDRAM_ARG1); +void swap_to_thread(RDRAM_ARG OSThread *to); +void pause_thread_impl(OSThread *t); +void pause_thread_native_impl(OSThread *t); +void resume_thread_impl(OSThread *t); +void resume_thread_native_impl(OSThread *t); +void schedule_running_thread(OSThread *t); +void stop_thread(OSThread *t); +void pause_self(RDRAM_ARG1); +void cleanup_thread(OSThread *t); +PTR(OSThread) this_thread(); +void disable_preemption(); +void enable_preemption(); +void notify_scheduler(); +void reprioritize_thread(OSThread *t, OSPri pri); +void set_main_thread(); +bool is_game_thread(); +void submit_rsp_task(RDRAM_ARG PTR(OSTask) task); +void send_si_message(); +uint32_t get_speed_multiplier(); +std::chrono::system_clock::time_point get_start(); +std::chrono::system_clock::duration time_since_start(); +void init_audio(); +void set_audio_frequency(uint32_t freq); +void queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data, uint32_t byte_count); +uint32_t get_remaining_audio_bytes(); + +class preemption_guard { +public: + preemption_guard(); + ~preemption_guard(); +private: + std::lock_guard lock; +}; + +} // namespace Multilibultra + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define debug_printf(...) +//#define debug_printf(...) printf(__VA_ARGS__); + +#endif diff --git a/portultra/platform_specific.h b/portultra/platform_specific.h new file mode 100644 index 0000000..b9d1823 --- /dev/null +++ b/portultra/platform_specific.h @@ -0,0 +1,32 @@ +#ifndef __PLATFORM_SPECIFIC_H__ +#define __PLATFORM_SPECIFIC_H__ + +#if defined(__linux__) + +//#include +// +//typedef struct { +// pthread_t t; +// pthread_barrier_t pause_barrier; +// pthread_mutex_t pause_mutex; +// pthread_cond_t pause_cond; +// void (*entrypoint)(void *); +// void *arg; +//} OSThreadNative; + +#elif defined(_WIN32) + +//#include +// +//typedef struct { +// pthread_t t; +// pthread_barrier_t pause_barrier; +// pthread_mutex_t pause_mutex; +// pthread_cond_t pause_cond; +// void (*entrypoint)(void*); +// void* arg; +//} OSThreadNative; + +#endif + +#endif \ No newline at end of file diff --git a/portultra/port_main.c b/portultra/port_main.c new file mode 100644 index 0000000..968b4d3 --- /dev/null +++ b/portultra/port_main.c @@ -0,0 +1,83 @@ +#if 0 + +#include +#include +#include "ultra64.h" + +#define THREAD_STACK_SIZE 0x1000 + +u8 idle_stack[THREAD_STACK_SIZE] ALIGNED(16); +u8 main_stack[THREAD_STACK_SIZE] ALIGNED(16); +u8 thread3_stack[THREAD_STACK_SIZE] ALIGNED(16); +u8 thread4_stack[THREAD_STACK_SIZE] ALIGNED(16); + +OSThread idle_thread; +OSThread main_thread; +OSThread thread3; +OSThread thread4; + +OSMesgQueue queue; +OSMesg buf[1]; + +void thread3_func(UNUSED void *arg) { + OSMesg val; + printf("Thread3 recv\n"); + fflush(stdout); + osRecvMesg(&queue, &val, OS_MESG_BLOCK); + printf("Thread3 complete: %d\n", (int)(intptr_t)val); + fflush(stdout); +} + +void thread4_func(void *arg) { + printf("Thread4 send %d\n", (int)(intptr_t)arg); + fflush(stdout); + osSendMesg(&queue, arg, OS_MESG_BLOCK); + printf("Thread4 complete\n"); + fflush(stdout); +} + +void main_thread_func(UNUSED void* arg) { + osCreateMesgQueue(&queue, buf, sizeof(buf) / sizeof(buf[0])); + + printf("main thread creating thread 3\n"); + osCreateThread(&thread3, 3, thread3_func, NULL, &thread3_stack[THREAD_STACK_SIZE], 14); + printf("main thread starting thread 3\n"); + osStartThread(&thread3); + + printf("main thread creating thread 4\n"); + osCreateThread(&thread4, 4, thread4_func, (void*)10, &thread4_stack[THREAD_STACK_SIZE], 13); + printf("main thread starting thread 4\n"); + osStartThread(&thread4); + + while (1) { + printf("main thread doin stuff\n"); + sleep(1); + } +} + +void idle_thread_func(UNUSED void* arg) { + printf("idle thread\n"); + printf("creating main thread\n"); + osCreateThread(&main_thread, 2, main_thread_func, NULL, &main_stack[THREAD_STACK_SIZE], 11); + printf("starting main thread\n"); + osStartThread(&main_thread); + + // Set this thread's priority to 0, making it the idle thread + osSetThreadPri(NULL, 0); + + // idle + while (1) { + printf("idle thread doin stuff\n"); + sleep(1); + } +} + +void bootproc(void) { + osInitialize(); + + osCreateThread(&idle_thread, 1, idle_thread_func, NULL, &idle_stack[THREAD_STACK_SIZE], 127); + printf("Starting idle thread\n"); + osStartThread(&idle_thread); +} + +#endif diff --git a/portultra/scheduler.cpp b/portultra/scheduler.cpp new file mode 100644 index 0000000..014739c --- /dev/null +++ b/portultra/scheduler.cpp @@ -0,0 +1,286 @@ +#include +#include +#include +#include + +#include "multilibultra.hpp" + +class OSThreadComparator { +public: + bool operator() (OSThread *a, OSThread *b) const { + return a->priority < b->priority; + } +}; + +class thread_queue_t : public std::priority_queue, OSThreadComparator> { +public: + // TODO comment this + bool remove(const OSThread* value) { + auto it = std::find(this->c.begin(), this->c.end(), value); + + if (it == this->c.end()) { + return false; + } + + if (it == this->c.begin()) { + // deque the top element + this->pop(); + } else { + // remove element and re-heap + this->c.erase(it); + std::make_heap(this->c.begin(), this->c.end(), this->comp); + } + + return true; + } +}; + +static struct { + std::vector to_schedule; + std::vector to_stop; + std::vector to_cleanup; + std::vector> to_reprioritize; + std::mutex mutex; + // OSThread* running_thread; + std::atomic_int notify_count; + std::atomic_int action_count; + + bool can_preempt; + std::mutex premption_mutex; +} scheduler_context{}; + +void handle_thread_queueing(thread_queue_t& running_thread_queue) { + std::lock_guard lock{scheduler_context.mutex}; + + if (!scheduler_context.to_schedule.empty()) { + OSThread* to_schedule = scheduler_context.to_schedule.back(); + scheduler_context.to_schedule.pop_back(); + scheduler_context.action_count.fetch_sub(1); + debug_printf("[Scheduler] Scheduling thread %d\n", to_schedule->id); + running_thread_queue.push(to_schedule); + } +} + +void handle_thread_stopping(thread_queue_t& running_thread_queue) { + std::lock_guard lock{scheduler_context.mutex}; + + while (!scheduler_context.to_stop.empty()) { + OSThread* to_stop = scheduler_context.to_stop.back(); + scheduler_context.to_stop.pop_back(); + scheduler_context.action_count.fetch_sub(1); + debug_printf("[Scheduler] Stopping thread %d\n", to_stop->id); + running_thread_queue.remove(to_stop); + } +} + +void handle_thread_cleanup(thread_queue_t& running_thread_queue) { + std::lock_guard lock{scheduler_context.mutex}; + + while (!scheduler_context.to_cleanup.empty()) { + OSThread* to_cleanup = scheduler_context.to_cleanup.back(); + scheduler_context.to_cleanup.pop_back(); + scheduler_context.action_count.fetch_sub(1); + + debug_printf("[Scheduler] Destroying thread %d\n", to_cleanup->id); + running_thread_queue.remove(to_cleanup); + to_cleanup->context->host_thread.join(); + delete to_cleanup->context; + } +} + +void handle_thread_reprioritization(thread_queue_t& running_thread_queue) { + std::lock_guard lock{scheduler_context.mutex}; + + while (!scheduler_context.to_reprioritize.empty()) { + const std::pair to_reprioritize = scheduler_context.to_reprioritize.back(); + scheduler_context.to_reprioritize.pop_back(); + scheduler_context.action_count.fetch_sub(1); + + debug_printf("[Scheduler] Reprioritizing thread %d to %d\n", to_reprioritize.first->id, to_reprioritize.second); + running_thread_queue.remove(to_reprioritize.first); + to_reprioritize.first->priority = to_reprioritize.second; + running_thread_queue.push(to_reprioritize.first); + } +} + +void handle_scheduler_notifications() { + std::lock_guard lock{scheduler_context.mutex}; + int32_t notify_count = scheduler_context.notify_count.exchange(0); + if (notify_count) { + debug_printf("Received %d notifications\n", notify_count); + scheduler_context.action_count.fetch_sub(notify_count); + } +} + +void swap_running_thread(thread_queue_t& running_thread_queue, OSThread*& cur_running_thread) { + if (running_thread_queue.size() > 0) { + OSThread* new_running_thread = running_thread_queue.top(); + if (cur_running_thread != new_running_thread) { + if (cur_running_thread && cur_running_thread->state == OSThreadState::RUNNING) { + debug_printf("[Scheduler] Need to wait for thread %d to pause itself\n", cur_running_thread->id); + return; + //debug_printf("[Scheduler] Switching execution from thread %d (%d) to thread %d (%d)\n", + // cur_running_thread->id, cur_running_thread->priority, + // new_running_thread->id, new_running_thread->priority); + //Multilibultra::pause_thread_impl(cur_running_thread); + } else { + debug_printf("[Scheduler] Switching execution to thread %d (%d)\n", new_running_thread->id, new_running_thread->priority); + } + Multilibultra::resume_thread_impl(new_running_thread); + cur_running_thread = new_running_thread; + } else if (cur_running_thread && cur_running_thread->state != OSThreadState::RUNNING) { + Multilibultra::resume_thread_impl(cur_running_thread); + } + } else { + cur_running_thread = nullptr; + } +} + +void scheduler_func() { + thread_queue_t running_thread_queue{}; + OSThread* cur_running_thread = nullptr; + + while (true) { + OSThread* old_running_thread = cur_running_thread; + scheduler_context.action_count.wait(0); + + std::lock_guard lock{scheduler_context.premption_mutex}; + + // Handle notifications + handle_scheduler_notifications(); + + // Handle stopping threads + handle_thread_stopping(running_thread_queue); + + // Handle cleaning up threads + handle_thread_cleanup(running_thread_queue); + + // Handle queueing threads to run + handle_thread_queueing(running_thread_queue); + + // Handle threads that have changed priority + handle_thread_reprioritization(running_thread_queue); + + // Determine which thread to run, stopping the current running thread if necessary + swap_running_thread(running_thread_queue, cur_running_thread); + + std::this_thread::yield(); + if (old_running_thread != cur_running_thread && old_running_thread && cur_running_thread) { + debug_printf("[Scheduler] Swapped from Thread %d (%d) to Thread %d (%d)\n", + old_running_thread->id, old_running_thread->priority, cur_running_thread->id, cur_running_thread->priority); + } + } +} + +extern "C" void do_yield() { + std::this_thread::yield(); +} + +namespace Multilibultra { + +void init_scheduler() { + scheduler_context.can_preempt = true; + std::thread scheduler_thread{scheduler_func}; + scheduler_thread.detach(); +} + +void schedule_running_thread(OSThread *t) { + debug_printf("[Scheduler] Queuing Thread %d to be scheduled\n", t->id); + std::lock_guard lock{scheduler_context.mutex}; + scheduler_context.to_schedule.push_back(t); + scheduler_context.action_count.fetch_add(1); + scheduler_context.action_count.notify_all(); +} + +void swap_to_thread(RDRAM_ARG OSThread *to) { + OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread()); + debug_printf("[Scheduler] Scheduling swap from thread %d to %d\n", self->id, to->id); + { + std::lock_guard lock{scheduler_context.mutex}; + scheduler_context.to_schedule.push_back(to); + Multilibultra::set_self_paused(PASS_RDRAM1); + scheduler_context.action_count.fetch_add(1); + scheduler_context.action_count.notify_all(); + } + Multilibultra::wait_for_resumed(PASS_RDRAM1); +} + +void reprioritize_thread(OSThread *t, OSPri pri) { + debug_printf("[Scheduler] Adjusting Thread %d priority to %d\n", t->id, pri); + std::lock_guard lock{scheduler_context.mutex}; + scheduler_context.to_reprioritize.emplace_back(t, pri); + scheduler_context.action_count.fetch_add(1); + scheduler_context.action_count.notify_all(); +} + +void pause_self(RDRAM_ARG1) { + OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread()); + debug_printf("[Scheduler] Thread %d pausing itself\n", self->id); + { + std::lock_guard lock{scheduler_context.mutex}; + Multilibultra::set_self_paused(PASS_RDRAM1); + scheduler_context.to_stop.push_back(self); + scheduler_context.action_count.fetch_add(1); + scheduler_context.action_count.notify_all(); + } + Multilibultra::wait_for_resumed(PASS_RDRAM1); +} + +void stop_thread(OSThread *t) { + debug_printf("[Scheduler] Queuing Thread %d to be stopped\n", t->id); + { + std::lock_guard lock{scheduler_context.mutex}; + scheduler_context.to_stop.push_back(t); + scheduler_context.action_count.fetch_add(1); + scheduler_context.action_count.notify_all(); + } + Multilibultra::pause_thread_impl(t); +} + +void cleanup_thread(OSThread *t) { + std::lock_guard lock{scheduler_context.mutex}; + scheduler_context.to_cleanup.push_back(t); + scheduler_context.action_count.fetch_add(1); + scheduler_context.action_count.notify_all(); +} + +void disable_preemption() { + scheduler_context.premption_mutex.lock(); + if (Multilibultra::is_game_thread()) { + scheduler_context.can_preempt = false; + } +} + +void enable_preemption() { + if (Multilibultra::is_game_thread()) { + scheduler_context.can_preempt = true; + } +#pragma warning(push) +#pragma warning( disable : 26110) + scheduler_context.premption_mutex.unlock(); +#pragma warning( pop ) +} + +// lock's constructor is called first, so can_preempt is set after locking +preemption_guard::preemption_guard() : lock{scheduler_context.premption_mutex} { + scheduler_context.can_preempt = false; +} + +// lock's destructor is called last, so can_preempt is set before unlocking +preemption_guard::~preemption_guard() { + scheduler_context.can_preempt = true; +} + +void notify_scheduler() { + std::lock_guard lock{scheduler_context.mutex}; + scheduler_context.notify_count.fetch_add(1); + scheduler_context.action_count.fetch_add(1); + scheduler_context.action_count.notify_all(); +} + +} + +extern "C" void pause_self(uint8_t* rdram) { + Multilibultra::pause_self(rdram); +} + diff --git a/portultra/task_pthreads.cpp b/portultra/task_pthreads.cpp new file mode 100644 index 0000000..667f022 --- /dev/null +++ b/portultra/task_pthreads.cpp @@ -0,0 +1,60 @@ +#ifndef _WIN32 + +// #include +// #include +// #include + +#include +#include +#include + +#include "ultra64.h" +#include "multilibultra.hpp" + +constexpr int pause_thread_signum = SIGUSR1; + +// void cleanup_current_thread(OSThread *t) { +// debug_printf("Thread cleanup %d\n", t->id); + +// // delete t->context; +// } + +void sig_handler(int signum, siginfo_t *info, void *context) { + if (signum == pause_thread_signum) { + OSThread *t = Multilibultra::this_thread(); + + debug_printf("[Sig] Thread %d paused\n", t->id); + + // Wait until the thread is marked as running again + t->context->running.wait(false); + + debug_printf("[Sig] Thread %d resumed\n", t->id); + } +} + +void Multilibultra::native_init(void) { + // Set up a signal handler to capture pause signals + struct sigaction sigact; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = SA_SIGINFO | SA_RESTART; + sigact.sa_sigaction = sig_handler; + + sigaction(pause_thread_signum, &sigact, nullptr); +} + +void Multilibultra::native_thread_init(OSThread *t) { + debug_printf("[Native] Init thread %d\n", t->id); +} + +void Multilibultra::pause_thread_native_impl(OSThread *t) { + debug_printf("[Native] Pause thread %d\n", t->id); + // Send a pause signal to the thread, which will trigger it to wait on its pause barrier in the signal handler + pthread_kill(t->context->host_thread.native_handle(), pause_thread_signum); +} + +void Multilibultra::resume_thread_native_impl(UNUSED OSThread *t) { + debug_printf("[Native] Resume thread %d\n", t->id); + // Nothing to do here +} + +#endif \ No newline at end of file diff --git a/portultra/task_win32.cpp b/portultra/task_win32.cpp new file mode 100644 index 0000000..06079ac --- /dev/null +++ b/portultra/task_win32.cpp @@ -0,0 +1,32 @@ +#include + +#include "ultra64.h" +#include "multilibultra.hpp" + +extern "C" unsigned int sleep(unsigned int seconds) { + Sleep(seconds * 1000); + return 0; +} + +void Multilibultra::native_init(void) { +} + +void Multilibultra::native_thread_init(OSThread *t) { + debug_printf("[Native] Init thread %d\n", t->id); +} + +void Multilibultra::pause_thread_native_impl(OSThread *t) { + debug_printf("[Native] Pause thread %d\n", t->id); + // Pause the thread via the win32 API + SuspendThread(t->context->host_thread.native_handle()); + // Perform a synchronous action to ensure that the thread is suspended + // see: https://devblogs.microsoft.com/oldnewthing/20150205-00/?p=44743 + CONTEXT threadContext{}; + GetThreadContext(t->context->host_thread.native_handle(), &threadContext); +} + +void Multilibultra::resume_thread_native_impl(UNUSED OSThread *t) { + debug_printf("[Native] Resume thread %d\n", t->id); + // Resume the thread + ResumeThread(t->context->host_thread.native_handle()); +} diff --git a/portultra/threads.cpp b/portultra/threads.cpp new file mode 100644 index 0000000..b83f561 --- /dev/null +++ b/portultra/threads.cpp @@ -0,0 +1,193 @@ +#include +#include +#include +#include + +#include "ultra64.h" +#include "multilibultra.hpp" + +// Native APIs only used to set thread names for easier debugging +#ifdef _WIN32 +#include +#endif + +extern "C" void bootproc(); + +thread_local bool is_main_thread = false; +// Whether this thread is part of the game (i.e. the start thread or one spawned by osCreateThread) +thread_local bool is_game_thread = false; +thread_local PTR(OSThread) thread_self = NULLPTR; + +void Multilibultra::set_main_thread() { + ::is_game_thread = true; + is_main_thread = true; +} + +bool Multilibultra::is_game_thread() { + return ::is_game_thread; +} + +#if 0 +int main(int argc, char** argv) { + Multilibultra::set_main_thread(); + + bootproc(); +} +#endif + +#if 1 +void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t arg); +#else +#define run_thread_function(func, sp, arg) func(arg) +#endif + +static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entrypoint, PTR(void) arg) { + OSThread *self = TO_PTR(OSThread, self_); + debug_printf("[Thread] Thread created: %d\n", self->id); + thread_self = self_; + is_game_thread = true; + + // Set the thread name +#ifdef _WIN32 + std::wstring thread_name = L"Game Thread " + std::to_wstring(self->id); + HRESULT r; + r = SetThreadDescription( + GetCurrentThread(), + thread_name.c_str() + ); +#endif + + // Perform any necessary native thread initialization. + Multilibultra::native_thread_init(self); + + // Set initialized to false to indicate that this thread can be started. + self->context->initialized.store(true); + self->context->initialized.notify_all(); + + debug_printf("[Thread] Thread waiting to be started: %d\n", self->id); + + // Wait until the thread is marked as running. + Multilibultra::set_self_paused(PASS_RDRAM1); + Multilibultra::wait_for_resumed(PASS_RDRAM1); + + debug_printf("[Thread] Thread started: %d\n", self->id); + + // Run the thread's function with the provided argument. + run_thread_function(PASS_RDRAM entrypoint, self->sp, arg); + + // Dispose of this thread after it completes. + Multilibultra::cleanup_thread(self); +} + +extern "C" void osStartThread(RDRAM_ARG PTR(OSThread) t_) { + OSThread* t = TO_PTR(OSThread, t_); + debug_printf("[os] Start Thread %d\n", t->id); + + // Wait until the thread is initialized to indicate that it's action_queued to be started. + t->context->initialized.wait(false); + + debug_printf("[os] Thread %d is ready to be started\n", t->id); + + if (thread_self && (t->priority > TO_PTR(OSThread, thread_self)->priority)) { + Multilibultra::swap_to_thread(PASS_RDRAM t); + } else { + Multilibultra::schedule_running_thread(t); + } + + // The main thread "becomes" the first thread started, so join on it and exit after it completes. + if (is_main_thread) { + t->context->host_thread.join(); + std::exit(EXIT_SUCCESS); + } +} + +extern "C" void osCreateThread(RDRAM_ARG PTR(OSThread) t_, OSId id, PTR(thread_func_t) entrypoint, PTR(void) arg, PTR(void) sp, OSPri pri) { + debug_printf("[os] Create Thread %d\n", id); + OSThread *t = TO_PTR(OSThread, t_); + + t->next = NULLPTR; + t->priority = pri; + t->id = id; + t->state = OSThreadState::PAUSED; + t->sp = sp - 0x10; // Set up the first stack frame + + // Spawn a new thread, which will immediately pause itself and wait until it's been started. + t->context = new UltraThreadContext{}; + t->context->initialized.store(false); + t->context->running.store(false); + + t->context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg}; +} + +extern "C" void osStopThread(RDRAM_ARG PTR(OSThread) t_) { + assert(false); +} + +extern "C" void osDestroyThread(RDRAM_ARG PTR(OSThread) t_) { + assert(false); +} + +extern "C" void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri) { + if (t == NULLPTR) { + t = thread_self; + } + bool pause_self = false; + if (pri > TO_PTR(OSThread, thread_self)->priority) { + pause_self = true; + Multilibultra::set_self_paused(PASS_RDRAM1); + } else if (t == thread_self && pri < TO_PTR(OSThread, thread_self)->priority) { + pause_self = true; + Multilibultra::set_self_paused(PASS_RDRAM1); + } + Multilibultra::reprioritize_thread(TO_PTR(OSThread, t), pri); + if (pause_self) { + Multilibultra::wait_for_resumed(PASS_RDRAM1); + } +} + +extern "C" OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) t) { + if (t == NULLPTR) { + t = thread_self; + } + return TO_PTR(OSThread, t)->priority; +} + +extern "C" OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t) { + if (t == NULLPTR) { + t = thread_self; + } + return TO_PTR(OSThread, t)->id; +} + +// TODO yield thread, need a stable priority queue in the scheduler + +void Multilibultra::set_self_paused(RDRAM_ARG1) { + debug_printf("[Thread] Thread pausing itself: %d\n", TO_PTR(OSThread, thread_self)->id); + TO_PTR(OSThread, thread_self)->state = OSThreadState::PAUSED; + TO_PTR(OSThread, thread_self)->context->running.store(false); + TO_PTR(OSThread, thread_self)->context->running.notify_all(); +} + +void Multilibultra::wait_for_resumed(RDRAM_ARG1) { + TO_PTR(OSThread, thread_self)->context->running.wait(false); +} + +void Multilibultra::pause_thread_impl(OSThread* t) { + t->state = OSThreadState::PREEMPTED; + t->context->running.store(false); + t->context->running.notify_all(); + Multilibultra::pause_thread_native_impl(t); +} + +void Multilibultra::resume_thread_impl(OSThread *t) { + if (t->state == OSThreadState::PREEMPTED) { + Multilibultra::resume_thread_native_impl(t); + } + t->state = OSThreadState::RUNNING; + t->context->running.store(true); + t->context->running.notify_all(); +} + +PTR(OSThread) Multilibultra::this_thread() { + return thread_self; +} diff --git a/portultra/timer.cpp b/portultra/timer.cpp new file mode 100644 index 0000000..039ef9c --- /dev/null +++ b/portultra/timer.cpp @@ -0,0 +1,192 @@ +#include +#include +#include +#include "blockingconcurrentqueue.h" + +#include "ultra64.h" +#include "multilibultra.hpp" +#include "recomp.h" + +// Start time for the program +static std::chrono::system_clock::time_point start = std::chrono::system_clock::now(); +// Game speed multiplier (1 means no speedup) +constexpr uint32_t speed_multiplier = 1; +// N64 CPU counter ticks per millisecond +constexpr uint32_t counter_per_ms = 46'875 * speed_multiplier; + +struct OSTimer { + PTR(OSTimer) unused1; + PTR(OSTimer) unused2; + OSTime interval; + OSTime timestamp; + PTR(OSMesgQueue) mq; + OSMesg msg; +}; + +struct AddTimerAction { + PTR(OSTask) timer; +}; + +struct RemoveTimerAction { + PTR(OSTimer) timer; +}; + +using Action = std::variant; + +struct { + std::thread thread; + moodycamel::BlockingConcurrentQueue action_queue{}; +} timer_context; + +uint64_t duration_to_ticks(std::chrono::system_clock::duration duration) { + uint64_t delta_micros = std::chrono::duration_cast(duration).count(); + // More accurate than using a floating point timer, will only overflow after running for 12.47 years + // Units: (micros * (counts/millis)) / (micros/millis) = counts + uint64_t total_count = (delta_micros * counter_per_ms) / 1000; + + return total_count; +} + +std::chrono::microseconds ticks_to_duration(uint64_t ticks) { + using namespace std::chrono_literals; + return ticks * 1000us / counter_per_ms; +} + +std::chrono::system_clock::time_point ticks_to_timepoint(uint64_t ticks) { + return start + ticks_to_duration(ticks); +} + +uint64_t time_now() { + return duration_to_ticks(std::chrono::system_clock::now() - start); +} + +void timer_thread(RDRAM_ARG1) { + // Lambda comparator function to keep the set ordered + auto timer_sort = [PASS_RDRAM1](PTR(OSTimer) a_, PTR(OSTimer) b_) { + OSTimer* a = TO_PTR(OSTimer, a_); + OSTimer* b = TO_PTR(OSTimer, b_); + + // Order by timestamp if the timers have different timestamps + if (a->timestamp != b->timestamp) { + return a->timestamp < b->timestamp; + } + + // If they have the exact same timestamp then order by address instead + return a < b; + }; + + // Ordered set of timers that are currently active + std::set active_timers{timer_sort}; + + // Lambda to process a timer action to handle adding and removing timers + auto process_timer_action = [&](const Action& action) { + // Determine the action type and act on it + if (const auto* add_action = std::get_if(&action)) { + active_timers.insert(add_action->timer); + } else if (const auto* remove_action = std::get_if(&action)) { + active_timers.erase(remove_action->timer); + } + }; + + while (true) { + // Empty the action queue + Action cur_action; + while (timer_context.action_queue.try_dequeue(cur_action)) { + process_timer_action(cur_action); + } + + // If there's no timer to act on, wait for one to come in from the action queue + while (active_timers.empty()) { + timer_context.action_queue.wait_dequeue(cur_action); + process_timer_action(cur_action); + } + + // Get the timer that's closest to running out + PTR(OSTimer) cur_timer_ = *active_timers.begin(); + OSTimer* cur_timer = TO_PTR(OSTimer, cur_timer_); + + // Remove the timer from the queue (it may get readded if waiting is interrupted) + active_timers.erase(cur_timer_); + + // Determine how long to wait to reach the timer's timestamp + auto wait_duration = ticks_to_timepoint(cur_timer->timestamp) - std::chrono::system_clock::now(); + auto wait_us = std::chrono::duration_cast(wait_duration); + + // Wait for either the duration to complete or a new action to come through + if (timer_context.action_queue.wait_dequeue_timed(cur_action, wait_duration)) { + // Timer was interrupted by a new action + // Add the current timer back to the queue (done first in case the action is to remove this timer) + active_timers.insert(cur_timer_); + // Process the new action + process_timer_action(cur_action); + } else { + // Waiting for the timer completed, so send the timer's message to its message queue + osSendMesg(PASS_RDRAM cur_timer->mq, cur_timer->msg, OS_MESG_NOBLOCK); + // If the timer has a specified interval then reload it with that value + if (cur_timer->interval != 0) { + cur_timer->timestamp = cur_timer->interval + time_now(); + active_timers.insert(cur_timer_); + } + } + } +} + +void Multilibultra::init_timers(RDRAM_ARG1) { + timer_context.thread = std::thread{ timer_thread, PASS_RDRAM1 }; +} + +uint32_t Multilibultra::get_speed_multiplier() { + return speed_multiplier; +} + +std::chrono::system_clock::time_point Multilibultra::get_start() { + return start; +} + +std::chrono::system_clock::duration Multilibultra::time_since_start() { + return std::chrono::system_clock::now() - start; +} + +extern "C" u32 osGetCount() { + uint64_t total_count = time_now(); + + // Allow for overflows, which is how osGetCount behaves + return (uint32_t)total_count; +} + +extern "C" OSTime osGetTime() { + uint64_t total_count = time_now(); + + return total_count; +} + +extern "C" int osSetTimer(RDRAM_ARG PTR(OSTimer) t_, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg) { + OSTimer* t = TO_PTR(OSTimer, t_); + + // HACK: Skip the RCP timeout detection + if ((countdown == 140625000 || countdown == 1500000) && (uintptr_t)msg == 666) { + return 0; + } + + // Determine the time when this timer will trigger off + if (countdown == 0) { + // Set the timestamp based on the interval + t->timestamp = interval + time_now(); + } else { + t->timestamp = countdown + time_now(); + } + t->interval = interval; + t->mq = mq; + t->msg = msg; + + timer_context.action_queue.enqueue(AddTimerAction{ t_ }); + + return 0; +} + +extern "C" int osStopTimer(RDRAM_ARG PTR(OSTimer) t_) { + timer_context.action_queue.enqueue(RemoveTimerAction{ t_ }); + + // TODO don't blindly return 0 here; requires some response from the timer thread to know what the returned value was + return 0; +} diff --git a/portultra/ultra64.h b/portultra/ultra64.h new file mode 100644 index 0000000..75d544a --- /dev/null +++ b/portultra/ultra64.h @@ -0,0 +1,221 @@ +#ifndef __ULTRA64_MULTILIBULTRA_H__ +#define __ULTRA64_MULTILIBULTRA_H__ + +#include +#include "platform_specific.h" + +#ifdef __cplusplus +#include +#endif + +#ifdef __GNUC__ +#define UNUSED __attribute__((unused)) +#define ALIGNED(x) __attribute__((aligned(x))) +#else +#define UNUSED +#define ALIGNED(x) +#endif + +typedef int64_t s64; +typedef uint64_t u64; +typedef int32_t s32; +typedef uint32_t u32; +typedef int16_t s16; +typedef uint16_t u16; +typedef int8_t s8; +typedef uint8_t u8; + +#define PTR(x) int32_t +#define RDRAM_ARG uint8_t *rdram, +#define RDRAM_ARG1 uint8_t *rdram +#define PASS_RDRAM rdram, +#define PASS_RDRAM1 rdram +#define TO_PTR(type, var) ((type*)(&rdram[(uint64_t)var - 0xFFFFFFFF80000000])) +#ifdef __cplusplus +#define NULLPTR (PTR(void))0 +#endif + +#ifndef NULL +#define NULL (PTR(void) 0) +#endif + +#define OS_MESG_NOBLOCK 0 +#define OS_MESG_BLOCK 1 + +typedef s32 OSPri; +typedef s32 OSId; + +typedef u64 OSTime; + +#define OS_EVENT_SW1 0 /* CPU SW1 interrupt */ +#define OS_EVENT_SW2 1 /* CPU SW2 interrupt */ +#define OS_EVENT_CART 2 /* Cartridge interrupt: used by rmon */ +#define OS_EVENT_COUNTER 3 /* Counter int: used by VI/Timer Mgr */ +#define OS_EVENT_SP 4 /* SP task done interrupt */ +#define OS_EVENT_SI 5 /* SI (controller) interrupt */ +#define OS_EVENT_AI 6 /* AI interrupt */ +#define OS_EVENT_VI 7 /* VI interrupt: used by VI/Timer Mgr */ +#define OS_EVENT_PI 8 /* PI interrupt: used by PI Manager */ +#define OS_EVENT_DP 9 /* DP full sync interrupt */ +#define OS_EVENT_CPU_BREAK 10 /* CPU breakpoint: used by rmon */ +#define OS_EVENT_SP_BREAK 11 /* SP breakpoint: used by rmon */ +#define OS_EVENT_FAULT 12 /* CPU fault event: used by rmon */ +#define OS_EVENT_THREADSTATUS 13 /* CPU thread status: used by rmon */ +#define OS_EVENT_PRENMI 14 /* Pre NMI interrupt */ + +#define M_GFXTASK 1 +#define M_AUDTASK 2 +#define M_VIDTASK 3 +#define M_NJPEGTASK 4 + +///////////// +// Structs // +///////////// + +// Threads + +typedef struct UltraThreadContext UltraThreadContext; + +typedef enum { + RUNNING, + PAUSED, + PREEMPTED +} OSThreadState; + +typedef struct OSThread_t { + PTR(struct OSThread_t) next; // Next thread in the given queue + OSPri priority; + uint32_t pad1; + uint32_t pad2; + uint16_t flags; // These two are swapped to reflect rdram byteswapping + uint16_t state; + OSId id; + int32_t pad3; + UltraThreadContext* context; // An actual pointer regardless of platform + int32_t sp; +} OSThread; + +typedef u32 OSEvent; +typedef PTR(void) OSMesg; + +typedef struct OSMesgQueue { + PTR(OSThread) blocked_on_recv; /* Linked list of threads blocked on receiving from this queue */ + PTR(OSThread) blocked_on_send; /* Linked list of threads blocked on sending to this queue */ + s32 validCount; /* Number of messages in the queue */ + s32 first; /* Index of the first message in the ring buffer */ + s32 msgCount; /* Size of message buffer */ + PTR(OSMesg) msg; /* Pointer to circular buffer to store messages */ +} OSMesgQueue; + +// RSP + +typedef struct { + u32 type; + u32 flags; + + PTR(u64) ucode_boot; + u32 ucode_boot_size; + + PTR(u64) ucode; + u32 ucode_size; + + PTR(u64) ucode_data; + u32 ucode_data_size; + + PTR(u64) dram_stack; + u32 dram_stack_size; + + PTR(u64) output_buff; + PTR(u64) output_buff_size; + + PTR(u64) data_ptr; + u32 data_size; + + PTR(u64) yield_data_ptr; + u32 yield_data_size; +} OSTask_t; + +typedef union { + OSTask_t t; + int64_t force_structure_alignment; +} OSTask; + +// PI + +struct OSIoMesgHdr { + // These 3 reversed due to endianness + u8 status; /* Return status */ + u8 pri; /* Message priority (High or Normal) */ + u16 type; /* Message type */ + PTR(OSMesgQueue) retQueue; /* Return message queue to notify I/O completion */ +}; + +struct OSIoMesg { + OSIoMesgHdr hdr; /* Message header */ + PTR(void) dramAddr; /* RDRAM buffer address (DMA) */ + u32 devAddr; /* Device buffer address (DMA) */ + u32 size; /* DMA transfer size in bytes */ + u32 piHandle; /* PI device handle */ +}; + +struct OSPiHandle { + PTR(OSPiHandle_s) unused; /* point to next handle on the table */ + // These four members reversed due to endianness + u8 relDuration; /* domain release duration */ + u8 pageSize; /* domain page size */ + u8 latency; /* domain latency */ + u8 type; /* DEVICE_TYPE_BULK for disk */ + // These three members reversed due to endianness + u16 padding; /* struct alignment padding */ + u8 domain; /* which domain */ + u8 pulse; /* domain pulse width */ + u32 baseAddress; /* Domain address */ + u32 speed; /* for roms only */ + /* The following are "private" elements" */ + u32 transferInfo[18]; /* for disk only */ +}; + +/////////////// +// Functions // +/////////////// + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +void osInitialize(void); + +typedef void (thread_func_t)(PTR(void)); + +void osCreateThread(RDRAM_ARG PTR(OSThread) t, OSId id, PTR(thread_func_t) entry, PTR(void) arg, PTR(void) sp, OSPri p); +void osStartThread(RDRAM_ARG PTR(OSThread) t); +void osStopThread(RDRAM_ARG PTR(OSThread) t); +void osDestroyThread(RDRAM_ARG PTR(OSThread) t); +void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri); +OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) thread); +OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t); + +s32 MQ_GET_COUNT(RDRAM_ARG PTR(OSMesgQueue)); +s32 MQ_IS_EMPTY(RDRAM_ARG PTR(OSMesgQueue)); +s32 MQ_IS_FULL(RDRAM_ARG PTR(OSMesgQueue)); + +void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32); +s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32); +s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32); +s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32); +void osSetEventMesg(RDRAM_ARG OSEvent, PTR(OSMesgQueue), OSMesg); +void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue), OSMesg, u32); +void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr); +PTR(void) osViGetNextFramebuffer(); +PTR(void) osViGetCurrentFramebuffer(); +u32 osGetCount(); +OSTime osGetTime(); +int osSetTimer(RDRAM_ARG PTR(OSTimer) timer, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg); +int osStopTimer(RDRAM_ARG PTR(OSTimer) timer); +u32 osVirtualToPhysical(PTR(void) addr); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/portultra/ultrainit.cpp b/portultra/ultrainit.cpp new file mode 100644 index 0000000..a95c06c --- /dev/null +++ b/portultra/ultrainit.cpp @@ -0,0 +1,15 @@ +#include "ultra64.h" +#include "multilibultra.hpp" + +void Multilibultra::preinit(uint8_t* rdram, uint8_t* rom) { + Multilibultra::set_main_thread(); + Multilibultra::init_events(rdram, rom); + Multilibultra::init_timers(rdram); + Multilibultra::init_audio(); + Multilibultra::save_init(); +} + +extern "C" void osInitialize() { + Multilibultra::init_scheduler(); + Multilibultra::native_init(); +} diff --git a/rsp/.gitignore b/rsp/.gitignore new file mode 100644 index 0000000..274200a --- /dev/null +++ b/rsp/.gitignore @@ -0,0 +1,2 @@ +aspMain.cpp +njpgdspMain.cpp diff --git a/src/ai.cpp b/src/ai.cpp new file mode 100644 index 0000000..835bb01 --- /dev/null +++ b/src/ai.cpp @@ -0,0 +1,29 @@ +#include "recomp.h" +#include +#include +#include "../portultra/ultra64.h" +#include "../portultra/multilibultra.hpp" + +#define VI_NTSC_CLOCK 48681812 + +extern "C" void osAiSetFrequency_recomp(uint8_t* rdram, recomp_context* ctx) { + uint32_t freq = ctx->r4; + // This makes actual audio frequency more accurate to console, but may not be desirable + //uint32_t dacRate = (uint32_t)(((float)VI_NTSC_CLOCK / freq) + 0.5f); + //freq = VI_NTSC_CLOCK / dacRate; + ctx->r2 = freq; + Multilibultra::set_audio_frequency(freq); +} + +extern "C" void osAiSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) { + Multilibultra::queue_audio_buffer(rdram, ctx->r4, ctx->r5); + ctx->r2 = 0; +} + +extern "C" void osAiGetLength_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = Multilibultra::get_remaining_audio_bytes(); +} + +extern "C" void osAiGetStatus_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = 0x00000000; // Pretend the audio DMAs finish instantly +} diff --git a/src/cont.cpp b/src/cont.cpp new file mode 100644 index 0000000..52283a2 --- /dev/null +++ b/src/cont.cpp @@ -0,0 +1,108 @@ +#include "../portultra/multilibultra.hpp" +#include "recomp.h" + +static int max_controllers = 0; + +extern "C" void osContInit_recomp(uint8_t* rdram, recomp_context* ctx) { + gpr bitpattern = ctx->r5; + gpr status = ctx->r6; + + // Set bit 0 to indicate that controller 0 is present + MEM_B(0, bitpattern) = 0x01; + + // Mark controller 0 as present + MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus) + MEM_B(2, status) = 0x00; // status: 0 (from joybus) + MEM_B(3, status) = 0x00; // errno: 0 (from libultra) + + max_controllers = 4; + + // Mark controllers 1-3 as not connected + for (size_t controller = 1; controller < max_controllers; controller++) { + // Libultra doesn't write status or type for absent controllers + MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4 + } + + ctx->r2 = 0; +} + +extern "C" void osContStartReadData_recomp(uint8_t* rdram, recomp_context* ctx) { + Multilibultra::send_si_message(); +} + +struct OSContPad { + u16 button; + s8 stick_x; /* -80 <= stick_x <= 80 */ + s8 stick_y; /* -80 <= stick_y <= 80 */ + u8 errno_; +}; + +int button = 0; +int stick_x = 0; +int stick_y = 0; + +void press_button(int button) { + +} + +void release_button(int button) { + +} + +extern "C" void osContGetReadData_recomp(uint8_t* rdram, recomp_context* ctx) { + int32_t pad = (int32_t)ctx->r4; + + if (max_controllers > 0) { + // button + MEM_H(0, pad) = button; + // stick_x + MEM_B(2, pad) = stick_x; + // stick_y + MEM_B(3, pad) = stick_y; + // errno + MEM_B(4, pad) = 0; + } + for (int controller = 1; controller < max_controllers; controller++) { + MEM_B(6 * controller + 4, pad) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4 + } +} + +extern "C" void osContStartQuery_recomp(uint8_t * rdram, recomp_context * ctx) { + Multilibultra::send_si_message(); +} + +extern "C" void osContGetQuery_recomp(uint8_t * rdram, recomp_context * ctx) { + gpr status = ctx->r4; + + // Mark controller 0 as present + MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus) + MEM_B(2, status) = 0x00; // status: 0 (from joybus) + MEM_B(3, status) = 0x00; // errno: 0 (from libultra) + + // Mark controllers 1-3 as not connected + for (size_t controller = 1; controller < max_controllers; controller++) { + // Libultra doesn't write status or type for absent controllers + MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4 + } +} + +extern "C" void osContSetCh_recomp(uint8_t* rdram, recomp_context* ctx) { + max_controllers = std::min((unsigned int)ctx->r4, 4u); + ctx->r2 = 0; +} + +extern "C" void __osMotorAccess_recomp(uint8_t* rdram, recomp_context* ctx) { + +} + +extern "C" void osMotorInit_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} + +extern "C" void osMotorStart_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} + +extern "C" void osMotorStop_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} diff --git a/src/dp.cpp b/src/dp.cpp new file mode 100644 index 0000000..3cf8e43 --- /dev/null +++ b/src/dp.cpp @@ -0,0 +1,5 @@ +#include "recomp.h" + +extern "C" void osDpSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} diff --git a/src/eep.cpp b/src/eep.cpp new file mode 100644 index 0000000..b6b04f9 --- /dev/null +++ b/src/eep.cpp @@ -0,0 +1,21 @@ +#include "recomp.h" + +extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} + +extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} + +extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} + +extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} + +extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} diff --git a/src/euc-jp.cpp b/src/euc-jp.cpp new file mode 100644 index 0000000..dbeb2fe --- /dev/null +++ b/src/euc-jp.cpp @@ -0,0 +1,2587 @@ +// Adapted from https://github.com/odashi/encoding +// Original license as follows: + +// MIT License +// +// Copyright (c) 2017 Yusuke Oda +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING +#include +#include +#include +#include +#include "euc-jp.h" + +namespace Encoding { + // JIS X 0201 to Unicode + const int jisx0201_2_unicode[] = { + // ASCII Compatible + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + // Shift_JIS First Byte + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + // Halfwidth Katakana + 0xfffd, 0xff61, 0xff62, 0xff63, 0xff64, 0xff65, 0xff66, 0xff67, + 0xff68, 0xff69, 0xff6a, 0xff6b, 0xff6c, 0xff6d, 0xff6e, 0xff6f, + 0xff70, 0xff71, 0xff72, 0xff73, 0xff74, 0xff75, 0xff76, 0xff77, + 0xff78, 0xff79, 0xff7a, 0xff7b, 0xff7c, 0xff7d, 0xff7e, 0xff7f, + 0xff80, 0xff81, 0xff82, 0xff83, 0xff84, 0xff85, 0xff86, 0xff87, + 0xff88, 0xff89, 0xff8a, 0xff8b, 0xff8c, 0xff8d, 0xff8e, 0xff8f, + 0xff90, 0xff91, 0xff92, 0xff93, 0xff94, 0xff95, 0xff96, 0xff97, + 0xff98, 0xff99, 0xff9a, 0xff9b, 0xff9c, 0xff9d, 0xff9e, 0xff9f, + // Shift_JIS First Byte + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd + }; + + // JIS X 0213 to Unicode + const int jisx0213_2_unicode[] = { + /* This table is converted by "JIS X 0213:2004 8-bit code vs Unicode mapping table" from x0213.org. + * License: + * Copyright (C) 2001 earthian@tama.or.jp, All Rights Reserved. + * Copyright (C) 2001 I'O, All Rights Reserved. + * Copyright (C) 2009 Project X0213, All Rights Reserved. + * You can use, modify, distribute this table freely. + */ + // plane 1 row 1 + 0x003000, 0x003001, 0x003002, 0x00002c, 0x00002e, 0x0030fb, 0x00003a, 0x00003b, + 0x00003f, 0x000021, 0x00309b, 0x00309c, 0x0000b4, 0x000060, 0x0000a8, 0x00005e, + 0x00203e, 0x00005f, 0x0030fd, 0x0030fe, 0x00309d, 0x00309e, 0x003003, 0x004edd, + 0x003005, 0x003006, 0x003007, 0x0030fc, 0x002014, 0x002010, 0x00002f, 0x00005c, + 0x00301c, 0x002016, 0x00007c, 0x002026, 0x002025, 0x002018, 0x002019, 0x00201c, + 0x00201d, 0x000028, 0x000029, 0x003014, 0x003015, 0x00005b, 0x00005d, 0x00007b, + 0x00007d, 0x003008, 0x003009, 0x00300a, 0x00300b, 0x00300c, 0x00300d, 0x00300e, + 0x00300f, 0x003010, 0x003011, 0x00002b, 0x002212, 0x0000b1, 0x0000d7, 0x0000f7, + 0x00003d, 0x002260, 0x00003c, 0x00003e, 0x002266, 0x002267, 0x00221e, 0x002234, + 0x002642, 0x002640, 0x0000b0, 0x002032, 0x002033, 0x002103, 0x0000a5, 0x000024, + 0x0000a2, 0x0000a3, 0x000025, 0x000023, 0x000026, 0x00002a, 0x000040, 0x0000a7, + 0x002606, 0x002605, 0x0025cb, 0x0025cf, 0x0025ce, 0x0025c7, + // plane 1 row 2 + 0x0025c6, 0x0025a1, 0x0025a0, 0x0025b3, 0x0025b2, 0x0025bd, 0x0025bc, 0x00203b, + 0x003012, 0x002192, 0x002190, 0x002191, 0x002193, 0x003013, 0x000027, 0x000022, + 0x00002d, 0x00007e, 0x003033, 0x003034, 0x003035, 0x00303b, 0x00303c, 0x0030ff, + 0x00309f, 0x002208, 0x00220b, 0x002286, 0x002287, 0x002282, 0x002283, 0x00222a, + 0x002229, 0x002284, 0x002285, 0x00228a, 0x00228b, 0x002209, 0x002205, 0x002305, + 0x002306, 0x002227, 0x002228, 0x0000ac, 0x0021d2, 0x0021d4, 0x002200, 0x002203, + 0x002295, 0x002296, 0x002297, 0x002225, 0x002226, 0x00ff5f, 0x00ff60, 0x003018, + 0x003019, 0x003016, 0x003017, 0x002220, 0x0022a5, 0x002312, 0x002202, 0x002207, + 0x002261, 0x002252, 0x00226a, 0x00226b, 0x00221a, 0x00223d, 0x00221d, 0x002235, + 0x00222b, 0x00222c, 0x002262, 0x002243, 0x002245, 0x002248, 0x002276, 0x002277, + 0x002194, 0x00212b, 0x002030, 0x00266f, 0x00266d, 0x00266a, 0x002020, 0x002021, + 0x0000b6, 0x00266e, 0x00266b, 0x00266c, 0x002669, 0x0025ef, + // plane 1 row 3 + 0x0025b7, 0x0025b6, 0x0025c1, 0x0025c0, 0x002197, 0x002198, 0x002196, 0x002199, + 0x0021c4, 0x0021e8, 0x0021e6, 0x0021e7, 0x0021e9, 0x002934, 0x002935, 0x000030, + 0x000031, 0x000032, 0x000033, 0x000034, 0x000035, 0x000036, 0x000037, 0x000038, + 0x000039, 0x0029bf, 0x0025c9, 0x00303d, 0x00fe46, 0x00fe45, 0x0025e6, 0x002022, + 0x000041, 0x000042, 0x000043, 0x000044, 0x000045, 0x000046, 0x000047, 0x000048, + 0x000049, 0x00004a, 0x00004b, 0x00004c, 0x00004d, 0x00004e, 0x00004f, 0x000050, + 0x000051, 0x000052, 0x000053, 0x000054, 0x000055, 0x000056, 0x000057, 0x000058, + 0x000059, 0x00005a, 0x002213, 0x002135, 0x00210f, 0x0033cb, 0x002113, 0x002127, + 0x000061, 0x000062, 0x000063, 0x000064, 0x000065, 0x000066, 0x000067, 0x000068, + 0x000069, 0x00006a, 0x00006b, 0x00006c, 0x00006d, 0x00006e, 0x00006f, 0x000070, + 0x000071, 0x000072, 0x000073, 0x000074, 0x000075, 0x000076, 0x000077, 0x000078, + 0x000079, 0x00007a, 0x0030a0, 0x002013, 0x0029fa, 0x0029fb, + // plane 1 row 4 + 0x003041, 0x003042, 0x003043, 0x003044, 0x003045, 0x003046, 0x003047, 0x003048, + 0x003049, 0x00304a, 0x00304b, 0x00304c, 0x00304d, 0x00304e, 0x00304f, 0x003050, + 0x003051, 0x003052, 0x003053, 0x003054, 0x003055, 0x003056, 0x003057, 0x003058, + 0x003059, 0x00305a, 0x00305b, 0x00305c, 0x00305d, 0x00305e, 0x00305f, 0x003060, + 0x003061, 0x003062, 0x003063, 0x003064, 0x003065, 0x003066, 0x003067, 0x003068, + 0x003069, 0x00306a, 0x00306b, 0x00306c, 0x00306d, 0x00306e, 0x00306f, 0x003070, + 0x003071, 0x003072, 0x003073, 0x003074, 0x003075, 0x003076, 0x003077, 0x003078, + 0x003079, 0x00307a, 0x00307b, 0x00307c, 0x00307d, 0x00307e, 0x00307f, 0x003080, + 0x003081, 0x003082, 0x003083, 0x003084, 0x003085, 0x003086, 0x003087, 0x003088, + 0x003089, 0x00308a, 0x00308b, 0x00308c, 0x00308d, 0x00308e, 0x00308f, 0x003090, + 0x003091, 0x003092, 0x003093, 0x003094, 0x003095, 0x003096, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 1 row 5 + 0x0030a1, 0x0030a2, 0x0030a3, 0x0030a4, 0x0030a5, 0x0030a6, 0x0030a7, 0x0030a8, + 0x0030a9, 0x0030aa, 0x0030ab, 0x0030ac, 0x0030ad, 0x0030ae, 0x0030af, 0x0030b0, + 0x0030b1, 0x0030b2, 0x0030b3, 0x0030b4, 0x0030b5, 0x0030b6, 0x0030b7, 0x0030b8, + 0x0030b9, 0x0030ba, 0x0030bb, 0x0030bc, 0x0030bd, 0x0030be, 0x0030bf, 0x0030c0, + 0x0030c1, 0x0030c2, 0x0030c3, 0x0030c4, 0x0030c5, 0x0030c6, 0x0030c7, 0x0030c8, + 0x0030c9, 0x0030ca, 0x0030cb, 0x0030cc, 0x0030cd, 0x0030ce, 0x0030cf, 0x0030d0, + 0x0030d1, 0x0030d2, 0x0030d3, 0x0030d4, 0x0030d5, 0x0030d6, 0x0030d7, 0x0030d8, + 0x0030d9, 0x0030da, 0x0030db, 0x0030dc, 0x0030dd, 0x0030de, 0x0030df, 0x0030e0, + 0x0030e1, 0x0030e2, 0x0030e3, 0x0030e4, 0x0030e5, 0x0030e6, 0x0030e7, 0x0030e8, + 0x0030e9, 0x0030ea, 0x0030eb, 0x0030ec, 0x0030ed, 0x0030ee, 0x0030ef, 0x0030f0, + 0x0030f1, 0x0030f2, 0x0030f3, 0x0030f4, 0x0030f5, 0x0030f6, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 1 row 6 + 0x000391, 0x000392, 0x000393, 0x000394, 0x000395, 0x000396, 0x000397, 0x000398, + 0x000399, 0x00039a, 0x00039b, 0x00039c, 0x00039d, 0x00039e, 0x00039f, 0x0003a0, + 0x0003a1, 0x0003a3, 0x0003a4, 0x0003a5, 0x0003a6, 0x0003a7, 0x0003a8, 0x0003a9, + 0x002664, 0x002660, 0x002662, 0x002666, 0x002661, 0x002665, 0x002667, 0x002663, + 0x0003b1, 0x0003b2, 0x0003b3, 0x0003b4, 0x0003b5, 0x0003b6, 0x0003b7, 0x0003b8, + 0x0003b9, 0x0003ba, 0x0003bb, 0x0003bc, 0x0003bd, 0x0003be, 0x0003bf, 0x0003c0, + 0x0003c1, 0x0003c3, 0x0003c4, 0x0003c5, 0x0003c6, 0x0003c7, 0x0003c8, 0x0003c9, + 0x0003c2, 0x0024f5, 0x0024f6, 0x0024f7, 0x0024f8, 0x0024f9, 0x0024fa, 0x0024fb, + 0x0024fc, 0x0024fd, 0x0024fe, 0x002616, 0x002617, 0x003020, 0x00260e, 0x002600, + 0x002601, 0x002602, 0x002603, 0x002668, 0x0025b1, 0x0031f0, 0x0031f1, 0x0031f2, + 0x0031f3, 0x0031f4, 0x0031f5, 0x0031f6, 0x0031f7, 0x0031f8, 0x0031f9, 0x00fffd, + 0x0031fa, 0x0031fb, 0x0031fc, 0x0031fd, 0x0031fe, 0x0031ff, + // plane 1 row 7 + 0x000410, 0x000411, 0x000412, 0x000413, 0x000414, 0x000415, 0x000401, 0x000416, + 0x000417, 0x000418, 0x000419, 0x00041a, 0x00041b, 0x00041c, 0x00041d, 0x00041e, + 0x00041f, 0x000420, 0x000421, 0x000422, 0x000423, 0x000424, 0x000425, 0x000426, + 0x000427, 0x000428, 0x000429, 0x00042a, 0x00042b, 0x00042c, 0x00042d, 0x00042e, + 0x00042f, 0x0023be, 0x0023bf, 0x0023c0, 0x0023c1, 0x0023c2, 0x0023c3, 0x0023c4, + 0x0023c5, 0x0023c6, 0x0023c7, 0x0023c8, 0x0023c9, 0x0023ca, 0x0023cb, 0x0023cc, + 0x000430, 0x000431, 0x000432, 0x000433, 0x000434, 0x000435, 0x000451, 0x000436, + 0x000437, 0x000438, 0x000439, 0x00043a, 0x00043b, 0x00043c, 0x00043d, 0x00043e, + 0x00043f, 0x000440, 0x000441, 0x000442, 0x000443, 0x000444, 0x000445, 0x000446, + 0x000447, 0x000448, 0x000449, 0x00044a, 0x00044b, 0x00044c, 0x00044d, 0x00044e, + 0x00044f, 0x0030f7, 0x0030f8, 0x0030f9, 0x0030fa, 0x0022da, 0x0022db, 0x002153, + 0x002154, 0x002155, 0x002713, 0x002318, 0x002423, 0x0023ce, + // plane 1 row 8 + 0x002500, 0x002502, 0x00250c, 0x002510, 0x002518, 0x002514, 0x00251c, 0x00252c, + 0x002524, 0x002534, 0x00253c, 0x002501, 0x002503, 0x00250f, 0x002513, 0x00251b, + 0x002517, 0x002523, 0x002533, 0x00252b, 0x00253b, 0x00254b, 0x002520, 0x00252f, + 0x002528, 0x002537, 0x00253f, 0x00251d, 0x002530, 0x002525, 0x002538, 0x002542, + 0x003251, 0x003252, 0x003253, 0x003254, 0x003255, 0x003256, 0x003257, 0x003258, + 0x003259, 0x00325a, 0x00325b, 0x00325c, 0x00325d, 0x00325e, 0x00325f, 0x0032b1, + 0x0032b2, 0x0032b3, 0x0032b4, 0x0032b5, 0x0032b6, 0x0032b7, 0x0032b8, 0x0032b9, + 0x0032ba, 0x0032bb, 0x0032bc, 0x0032bd, 0x0032be, 0x0032bf, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x0025d0, 0x0025d1, + 0x0025d2, 0x0025d3, 0x00203c, 0x002047, 0x002048, 0x002049, 0x0001cd, 0x0001ce, + 0x0001d0, 0x001e3e, 0x001e3f, 0x0001f8, 0x0001f9, 0x0001d1, 0x0001d2, 0x0001d4, + 0x0001d6, 0x0001d8, 0x0001da, 0x0001dc, 0x00fffd, 0x00fffd, + // plane 1 row 9 + 0x0020ac, 0x0000a0, 0x0000a1, 0x0000a4, 0x0000a6, 0x0000a9, 0x0000aa, 0x0000ab, + 0x0000ad, 0x0000ae, 0x0000af, 0x0000b2, 0x0000b3, 0x0000b7, 0x0000b8, 0x0000b9, + 0x0000ba, 0x0000bb, 0x0000bc, 0x0000bd, 0x0000be, 0x0000bf, 0x0000c0, 0x0000c1, + 0x0000c2, 0x0000c3, 0x0000c4, 0x0000c5, 0x0000c6, 0x0000c7, 0x0000c8, 0x0000c9, + 0x0000ca, 0x0000cb, 0x0000cc, 0x0000cd, 0x0000ce, 0x0000cf, 0x0000d0, 0x0000d1, + 0x0000d2, 0x0000d3, 0x0000d4, 0x0000d5, 0x0000d6, 0x0000d8, 0x0000d9, 0x0000da, + 0x0000db, 0x0000dc, 0x0000dd, 0x0000de, 0x0000df, 0x0000e0, 0x0000e1, 0x0000e2, + 0x0000e3, 0x0000e4, 0x0000e5, 0x0000e6, 0x0000e7, 0x0000e8, 0x0000e9, 0x0000ea, + 0x0000eb, 0x0000ec, 0x0000ed, 0x0000ee, 0x0000ef, 0x0000f0, 0x0000f1, 0x0000f2, + 0x0000f3, 0x0000f4, 0x0000f5, 0x0000f6, 0x0000f8, 0x0000f9, 0x0000fa, 0x0000fb, + 0x0000fc, 0x0000fd, 0x0000fe, 0x0000ff, 0x000100, 0x00012a, 0x00016a, 0x000112, + 0x00014c, 0x000101, 0x00012b, 0x00016b, 0x000113, 0x00014d, + // plane 1 row 10 + 0x000104, 0x0002d8, 0x000141, 0x00013d, 0x00015a, 0x000160, 0x00015e, 0x000164, + 0x000179, 0x00017d, 0x00017b, 0x000105, 0x0002db, 0x000142, 0x00013e, 0x00015b, + 0x0002c7, 0x000161, 0x00015f, 0x000165, 0x00017a, 0x0002dd, 0x00017e, 0x00017c, + 0x000154, 0x000102, 0x000139, 0x000106, 0x00010c, 0x000118, 0x00011a, 0x00010e, + 0x000143, 0x000147, 0x000150, 0x000158, 0x00016e, 0x000170, 0x000162, 0x000155, + 0x000103, 0x00013a, 0x000107, 0x00010d, 0x000119, 0x00011b, 0x00010f, 0x000111, + 0x000144, 0x000148, 0x000151, 0x000159, 0x00016f, 0x000171, 0x000163, 0x0002d9, + 0x000108, 0x00011c, 0x000124, 0x000134, 0x00015c, 0x00016c, 0x000109, 0x00011d, + 0x000125, 0x000135, 0x00015d, 0x00016d, 0x000271, 0x00028b, 0x00027e, 0x000283, + 0x000292, 0x00026c, 0x00026e, 0x000279, 0x000288, 0x000256, 0x000273, 0x00027d, + 0x000282, 0x000290, 0x00027b, 0x00026d, 0x00025f, 0x000272, 0x00029d, 0x00028e, + 0x000261, 0x00014b, 0x000270, 0x000281, 0x000127, 0x000295, + // plane 1 row 11 + 0x000294, 0x000266, 0x000298, 0x0001c2, 0x000253, 0x000257, 0x000284, 0x000260, + 0x000193, 0x000153, 0x000152, 0x000268, 0x000289, 0x000258, 0x000275, 0x000259, + 0x00025c, 0x00025e, 0x000250, 0x00026f, 0x00028a, 0x000264, 0x00028c, 0x000254, + 0x000251, 0x000252, 0x00028d, 0x000265, 0x0002a2, 0x0002a1, 0x000255, 0x000291, + 0x00027a, 0x000267, 0x00025a, 0x00fffd, 0x0001fd, 0x001f70, 0x001f71, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x001f72, + 0x001f73, 0x000361, 0x0002c8, 0x0002cc, 0x0002d0, 0x0002d1, 0x000306, 0x00203f, + 0x00030b, 0x000301, 0x000304, 0x000300, 0x00030f, 0x00030c, 0x000302, 0x0002e5, + 0x0002e6, 0x0002e7, 0x0002e8, 0x0002e9, 0x00fffd, 0x00fffd, 0x000325, 0x00032c, + 0x000339, 0x00031c, 0x00031f, 0x000320, 0x000308, 0x00033d, 0x000329, 0x00032f, + 0x0002de, 0x000324, 0x000330, 0x00033c, 0x000334, 0x00031d, 0x00031e, 0x000318, + 0x000319, 0x00032a, 0x00033a, 0x00033b, 0x000303, 0x00031a, + // plane 1 row 12 + 0x002776, 0x002777, 0x002778, 0x002779, 0x00277a, 0x00277b, 0x00277c, 0x00277d, + 0x00277e, 0x00277f, 0x0024eb, 0x0024ec, 0x0024ed, 0x0024ee, 0x0024ef, 0x0024f0, + 0x0024f1, 0x0024f2, 0x0024f3, 0x0024f4, 0x002170, 0x002171, 0x002172, 0x002173, + 0x002174, 0x002175, 0x002176, 0x002177, 0x002178, 0x002179, 0x00217a, 0x00217b, + 0x0024d0, 0x0024d1, 0x0024d2, 0x0024d3, 0x0024d4, 0x0024d5, 0x0024d6, 0x0024d7, + 0x0024d8, 0x0024d9, 0x0024da, 0x0024db, 0x0024dc, 0x0024dd, 0x0024de, 0x0024df, + 0x0024e0, 0x0024e1, 0x0024e2, 0x0024e3, 0x0024e4, 0x0024e5, 0x0024e6, 0x0024e7, + 0x0024e8, 0x0024e9, 0x0032d0, 0x0032d1, 0x0032d2, 0x0032d3, 0x0032d4, 0x0032d5, + 0x0032d6, 0x0032d7, 0x0032d8, 0x0032d9, 0x0032da, 0x0032db, 0x0032dc, 0x0032dd, + 0x0032de, 0x0032df, 0x0032e0, 0x0032e1, 0x0032e2, 0x0032e3, 0x0032fa, 0x0032e9, + 0x0032e5, 0x0032ed, 0x0032ec, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x002051, 0x002042, + // plane 1 row 13 + 0x002460, 0x002461, 0x002462, 0x002463, 0x002464, 0x002465, 0x002466, 0x002467, + 0x002468, 0x002469, 0x00246a, 0x00246b, 0x00246c, 0x00246d, 0x00246e, 0x00246f, + 0x002470, 0x002471, 0x002472, 0x002473, 0x002160, 0x002161, 0x002162, 0x002163, + 0x002164, 0x002165, 0x002166, 0x002167, 0x002168, 0x002169, 0x00216a, 0x003349, + 0x003314, 0x003322, 0x00334d, 0x003318, 0x003327, 0x003303, 0x003336, 0x003351, + 0x003357, 0x00330d, 0x003326, 0x003323, 0x00332b, 0x00334a, 0x00333b, 0x00339c, + 0x00339d, 0x00339e, 0x00338e, 0x00338f, 0x0033c4, 0x0033a1, 0x00216b, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00337b, 0x00301d, + 0x00301f, 0x002116, 0x0033cd, 0x002121, 0x0032a4, 0x0032a5, 0x0032a6, 0x0032a7, + 0x0032a8, 0x003231, 0x003232, 0x003239, 0x00337e, 0x00337d, 0x00337c, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00222e, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00221f, + 0x0022bf, 0x00fffd, 0x00fffd, 0x00fffd, 0x002756, 0x00261e, + // plane 1 row 14 + 0x004ff1, 0x02000b, 0x003402, 0x004e28, 0x004e2f, 0x004e30, 0x004e8d, 0x004ee1, + 0x004efd, 0x004eff, 0x004f03, 0x004f0b, 0x004f60, 0x004f48, 0x004f49, 0x004f56, + 0x004f5f, 0x004f6a, 0x004f6c, 0x004f7e, 0x004f8a, 0x004f94, 0x004f97, 0x00fa30, + 0x004fc9, 0x004fe0, 0x005001, 0x005002, 0x00500e, 0x005018, 0x005027, 0x00502e, + 0x005040, 0x00503b, 0x005041, 0x005094, 0x0050cc, 0x0050f2, 0x0050d0, 0x0050e6, + 0x00fa31, 0x005106, 0x005103, 0x00510b, 0x00511e, 0x005135, 0x00514a, 0x00fa32, + 0x005155, 0x005157, 0x0034b5, 0x00519d, 0x0051c3, 0x0051ca, 0x0051de, 0x0051e2, + 0x0051ee, 0x005201, 0x0034db, 0x005213, 0x005215, 0x005249, 0x005257, 0x005261, + 0x005293, 0x0052c8, 0x00fa33, 0x0052cc, 0x0052d0, 0x0052d6, 0x0052db, 0x00fa34, + 0x0052f0, 0x0052fb, 0x005300, 0x005307, 0x00531c, 0x00fa35, 0x005361, 0x005363, + 0x00537d, 0x005393, 0x00539d, 0x0053b2, 0x005412, 0x005427, 0x00544d, 0x00549c, + 0x00546b, 0x005474, 0x00547f, 0x005488, 0x005496, 0x0054a1, + // plane 1 row 15 + 0x0054a9, 0x0054c6, 0x0054ff, 0x00550e, 0x00552b, 0x005535, 0x005550, 0x00555e, + 0x005581, 0x005586, 0x00558e, 0x00fa36, 0x0055ad, 0x0055ce, 0x00fa37, 0x005608, + 0x00560e, 0x00563b, 0x005649, 0x005676, 0x005666, 0x00fa38, 0x00566f, 0x005671, + 0x005672, 0x005699, 0x00569e, 0x0056a9, 0x0056ac, 0x0056b3, 0x0056c9, 0x0056ca, + 0x00570a, 0x02123d, 0x005721, 0x00572f, 0x005733, 0x005734, 0x005770, 0x005777, + 0x00577c, 0x00579c, 0x00fa0f, 0x02131b, 0x0057b8, 0x0057c7, 0x0057c8, 0x0057cf, + 0x0057e4, 0x0057ed, 0x0057f5, 0x0057f6, 0x0057ff, 0x005809, 0x00fa10, 0x005861, + 0x005864, 0x00fa39, 0x00587c, 0x005889, 0x00589e, 0x00fa3a, 0x0058a9, 0x02146e, + 0x0058d2, 0x0058ce, 0x0058d4, 0x0058da, 0x0058e0, 0x0058e9, 0x00590c, 0x008641, + 0x00595d, 0x00596d, 0x00598b, 0x005992, 0x0059a4, 0x0059c3, 0x0059d2, 0x0059dd, + 0x005a13, 0x005a23, 0x005a67, 0x005a6d, 0x005a77, 0x005a7e, 0x005a84, 0x005a9e, + 0x005aa7, 0x005ac4, 0x0218bd, 0x005b19, 0x005b25, 0x00525d, + // plane 1 row 16 + 0x004e9c, 0x005516, 0x005a03, 0x00963f, 0x0054c0, 0x00611b, 0x006328, 0x0059f6, + 0x009022, 0x008475, 0x00831c, 0x007a50, 0x0060aa, 0x0063e1, 0x006e25, 0x0065ed, + 0x008466, 0x0082a6, 0x009bf5, 0x006893, 0x005727, 0x0065a1, 0x006271, 0x005b9b, + 0x0059d0, 0x00867b, 0x0098f4, 0x007d62, 0x007dbe, 0x009b8e, 0x006216, 0x007c9f, + 0x0088b7, 0x005b89, 0x005eb5, 0x006309, 0x006697, 0x006848, 0x0095c7, 0x00978d, + 0x00674f, 0x004ee5, 0x004f0a, 0x004f4d, 0x004f9d, 0x005049, 0x0056f2, 0x005937, + 0x0059d4, 0x005a01, 0x005c09, 0x0060df, 0x00610f, 0x006170, 0x006613, 0x006905, + 0x0070ba, 0x00754f, 0x007570, 0x0079fb, 0x007dad, 0x007def, 0x0080c3, 0x00840e, + 0x008863, 0x008b02, 0x009055, 0x00907a, 0x00533b, 0x004e95, 0x004ea5, 0x0057df, + 0x0080b2, 0x0090c1, 0x0078ef, 0x004e00, 0x0058f1, 0x006ea2, 0x009038, 0x007a32, + 0x008328, 0x00828b, 0x009c2f, 0x005141, 0x005370, 0x0054bd, 0x0054e1, 0x0056e0, + 0x0059fb, 0x005f15, 0x0098f2, 0x006deb, 0x0080e4, 0x00852d, + // plane 1 row 17 + 0x009662, 0x009670, 0x0096a0, 0x0097fb, 0x00540b, 0x0053f3, 0x005b87, 0x0070cf, + 0x007fbd, 0x008fc2, 0x0096e8, 0x00536f, 0x009d5c, 0x007aba, 0x004e11, 0x007893, + 0x0081fc, 0x006e26, 0x005618, 0x005504, 0x006b1d, 0x00851a, 0x009c3b, 0x0059e5, + 0x0053a9, 0x006d66, 0x0074dc, 0x00958f, 0x005642, 0x004e91, 0x00904b, 0x0096f2, + 0x00834f, 0x00990c, 0x0053e1, 0x0055b6, 0x005b30, 0x005f71, 0x006620, 0x0066f3, + 0x006804, 0x006c38, 0x006cf3, 0x006d29, 0x00745b, 0x0076c8, 0x007a4e, 0x009834, + 0x0082f1, 0x00885b, 0x008a60, 0x0092ed, 0x006db2, 0x0075ab, 0x0076ca, 0x0099c5, + 0x0060a6, 0x008b01, 0x008d8a, 0x0095b2, 0x00698e, 0x0053ad, 0x005186, 0x005712, + 0x005830, 0x005944, 0x005bb4, 0x005ef6, 0x006028, 0x0063a9, 0x0063f4, 0x006cbf, + 0x006f14, 0x00708e, 0x007114, 0x007159, 0x0071d5, 0x00733f, 0x007e01, 0x008276, + 0x0082d1, 0x008597, 0x009060, 0x00925b, 0x009d1b, 0x005869, 0x0065bc, 0x006c5a, + 0x007525, 0x0051f9, 0x00592e, 0x005965, 0x005f80, 0x005fdc, + // plane 1 row 18 + 0x0062bc, 0x0065fa, 0x006a2a, 0x006b27, 0x006bb4, 0x00738b, 0x007fc1, 0x008956, + 0x009d2c, 0x009d0e, 0x009ec4, 0x005ca1, 0x006c96, 0x00837b, 0x005104, 0x005c4b, + 0x0061b6, 0x0081c6, 0x006876, 0x007261, 0x004e59, 0x004ffa, 0x005378, 0x006069, + 0x006e29, 0x007a4f, 0x0097f3, 0x004e0b, 0x005316, 0x004eee, 0x004f55, 0x004f3d, + 0x004fa1, 0x004f73, 0x0052a0, 0x0053ef, 0x005609, 0x00590f, 0x005ac1, 0x005bb6, + 0x005be1, 0x0079d1, 0x006687, 0x00679c, 0x0067b6, 0x006b4c, 0x006cb3, 0x00706b, + 0x0073c2, 0x00798d, 0x0079be, 0x007a3c, 0x007b87, 0x0082b1, 0x0082db, 0x008304, + 0x008377, 0x0083ef, 0x0083d3, 0x008766, 0x008ab2, 0x005629, 0x008ca8, 0x008fe6, + 0x00904e, 0x00971e, 0x00868a, 0x004fc4, 0x005ce8, 0x006211, 0x007259, 0x00753b, + 0x0081e5, 0x0082bd, 0x0086fe, 0x008cc0, 0x0096c5, 0x009913, 0x0099d5, 0x004ecb, + 0x004f1a, 0x0089e3, 0x0056de, 0x00584a, 0x0058ca, 0x005efb, 0x005feb, 0x00602a, + 0x006094, 0x006062, 0x0061d0, 0x006212, 0x0062d0, 0x006539, + // plane 1 row 19 + 0x009b41, 0x006666, 0x0068b0, 0x006d77, 0x007070, 0x00754c, 0x007686, 0x007d75, + 0x0082a5, 0x0087f9, 0x00958b, 0x00968e, 0x008c9d, 0x0051f1, 0x0052be, 0x005916, + 0x0054b3, 0x005bb3, 0x005d16, 0x006168, 0x006982, 0x006daf, 0x00788d, 0x0084cb, + 0x008857, 0x008a72, 0x0093a7, 0x009ab8, 0x006d6c, 0x0099a8, 0x0086d9, 0x0057a3, + 0x0067ff, 0x0086ce, 0x00920e, 0x005283, 0x005687, 0x005404, 0x005ed3, 0x0062e1, + 0x0064b9, 0x00683c, 0x006838, 0x006bbb, 0x007372, 0x0078ba, 0x007a6b, 0x00899a, + 0x0089d2, 0x008d6b, 0x008f03, 0x0090ed, 0x0095a3, 0x009694, 0x009769, 0x005b66, + 0x005cb3, 0x00697d, 0x00984d, 0x00984e, 0x00639b, 0x007b20, 0x006a2b, 0x006a7f, + 0x0068b6, 0x009c0d, 0x006f5f, 0x005272, 0x00559d, 0x006070, 0x0062ec, 0x006d3b, + 0x006e07, 0x006ed1, 0x00845b, 0x008910, 0x008f44, 0x004e14, 0x009c39, 0x0053f6, + 0x00691b, 0x006a3a, 0x009784, 0x00682a, 0x00515c, 0x007ac3, 0x0084b2, 0x0091dc, + 0x00938c, 0x00565b, 0x009d28, 0x006822, 0x008305, 0x008431, + // plane 1 row 20 + 0x007ca5, 0x005208, 0x0082c5, 0x0074e6, 0x004e7e, 0x004f83, 0x0051a0, 0x005bd2, + 0x00520a, 0x0052d8, 0x0052e7, 0x005dfb, 0x00559a, 0x00582a, 0x0059e6, 0x005b8c, + 0x005b98, 0x005bdb, 0x005e72, 0x005e79, 0x0060a3, 0x00611f, 0x006163, 0x0061be, + 0x0063db, 0x006562, 0x0067d1, 0x006853, 0x0068fa, 0x006b3e, 0x006b53, 0x006c57, + 0x006f22, 0x006f97, 0x006f45, 0x0074b0, 0x007518, 0x0076e3, 0x00770b, 0x007aff, + 0x007ba1, 0x007c21, 0x007de9, 0x007f36, 0x007ff0, 0x00809d, 0x008266, 0x00839e, + 0x0089b3, 0x008acc, 0x008cab, 0x009084, 0x009451, 0x009593, 0x009591, 0x0095a2, + 0x009665, 0x0097d3, 0x009928, 0x008218, 0x004e38, 0x00542b, 0x005cb8, 0x005dcc, + 0x0073a9, 0x00764c, 0x00773c, 0x005ca9, 0x007feb, 0x008d0b, 0x0096c1, 0x009811, + 0x009854, 0x009858, 0x004f01, 0x004f0e, 0x005371, 0x00559c, 0x005668, 0x0057fa, + 0x005947, 0x005b09, 0x005bc4, 0x005c90, 0x005e0c, 0x005e7e, 0x005fcc, 0x0063ee, + 0x00673a, 0x0065d7, 0x0065e2, 0x00671f, 0x0068cb, 0x0068c4, + // plane 1 row 21 + 0x006a5f, 0x005e30, 0x006bc5, 0x006c17, 0x006c7d, 0x00757f, 0x007948, 0x005b63, + 0x007a00, 0x007d00, 0x005fbd, 0x00898f, 0x008a18, 0x008cb4, 0x008d77, 0x008ecc, + 0x008f1d, 0x0098e2, 0x009a0e, 0x009b3c, 0x004e80, 0x00507d, 0x005100, 0x005993, + 0x005b9c, 0x00622f, 0x006280, 0x0064ec, 0x006b3a, 0x0072a0, 0x007591, 0x007947, + 0x007fa9, 0x0087fb, 0x008abc, 0x008b70, 0x0063ac, 0x0083ca, 0x0097a0, 0x005409, + 0x005403, 0x0055ab, 0x006854, 0x006a58, 0x008a70, 0x007827, 0x006775, 0x009ecd, + 0x005374, 0x005ba2, 0x00811a, 0x008650, 0x009006, 0x004e18, 0x004e45, 0x004ec7, + 0x004f11, 0x0053ca, 0x005438, 0x005bae, 0x005f13, 0x006025, 0x006551, 0x00673d, + 0x006c42, 0x006c72, 0x006ce3, 0x007078, 0x007403, 0x007a76, 0x007aae, 0x007b08, + 0x007d1a, 0x007cfe, 0x007d66, 0x0065e7, 0x00725b, 0x0053bb, 0x005c45, 0x005de8, + 0x0062d2, 0x0062e0, 0x006319, 0x006e20, 0x00865a, 0x008a31, 0x008ddd, 0x0092f8, + 0x006f01, 0x0079a6, 0x009b5a, 0x004ea8, 0x004eab, 0x004eac, + // plane 1 row 22 + 0x004f9b, 0x004fa0, 0x0050d1, 0x005147, 0x007af6, 0x005171, 0x0051f6, 0x005354, + 0x005321, 0x00537f, 0x0053eb, 0x0055ac, 0x005883, 0x005ce1, 0x005f37, 0x005f4a, + 0x00602f, 0x006050, 0x00606d, 0x00631f, 0x006559, 0x006a4b, 0x006cc1, 0x0072c2, + 0x0072ed, 0x0077ef, 0x0080f8, 0x008105, 0x008208, 0x00854e, 0x0090f7, 0x0093e1, + 0x0097ff, 0x009957, 0x009a5a, 0x004ef0, 0x0051dd, 0x005c2d, 0x006681, 0x00696d, + 0x005c40, 0x0066f2, 0x006975, 0x007389, 0x006850, 0x007c81, 0x0050c5, 0x0052e4, + 0x005747, 0x005dfe, 0x009326, 0x0065a4, 0x006b23, 0x006b3d, 0x007434, 0x007981, + 0x0079bd, 0x007b4b, 0x007dca, 0x0082b9, 0x0083cc, 0x00887f, 0x00895f, 0x008b39, + 0x008fd1, 0x0091d1, 0x00541f, 0x009280, 0x004e5d, 0x005036, 0x0053e5, 0x00533a, + 0x0072d7, 0x007396, 0x0077e9, 0x0082e6, 0x008eaf, 0x0099c6, 0x0099c8, 0x0099d2, + 0x005177, 0x00611a, 0x00865e, 0x0055b0, 0x007a7a, 0x005076, 0x005bd3, 0x009047, + 0x009685, 0x004e32, 0x006adb, 0x0091e7, 0x005c51, 0x005c48, + // plane 1 row 23 + 0x006398, 0x007a9f, 0x006c93, 0x009774, 0x008f61, 0x007aaa, 0x00718a, 0x009688, + 0x007c82, 0x006817, 0x007e70, 0x006851, 0x00936c, 0x0052f2, 0x00541b, 0x0085ab, + 0x008a13, 0x007fa4, 0x008ecd, 0x0090e1, 0x005366, 0x008888, 0x007941, 0x004fc2, + 0x0050be, 0x005211, 0x005144, 0x005553, 0x00572d, 0x0073ea, 0x00578b, 0x005951, + 0x005f62, 0x005f84, 0x006075, 0x006176, 0x006167, 0x0061a9, 0x0063b2, 0x00643a, + 0x00656c, 0x00666f, 0x006842, 0x006e13, 0x007566, 0x007a3d, 0x007cfb, 0x007d4c, + 0x007d99, 0x007e4b, 0x007f6b, 0x00830e, 0x00834a, 0x0086cd, 0x008a08, 0x008a63, + 0x008b66, 0x008efd, 0x00981a, 0x009d8f, 0x0082b8, 0x008fce, 0x009be8, 0x005287, + 0x00621f, 0x006483, 0x006fc0, 0x009699, 0x006841, 0x005091, 0x006b20, 0x006c7a, + 0x006f54, 0x007a74, 0x007d50, 0x008840, 0x008a23, 0x006708, 0x004ef6, 0x005039, + 0x005026, 0x005065, 0x00517c, 0x005238, 0x005263, 0x0055a7, 0x00570f, 0x005805, + 0x005acc, 0x005efa, 0x0061b2, 0x0061f8, 0x0062f3, 0x006372, + // plane 1 row 24 + 0x00691c, 0x006a29, 0x00727d, 0x0072ac, 0x00732e, 0x007814, 0x00786f, 0x007d79, + 0x00770c, 0x0080a9, 0x00898b, 0x008b19, 0x008ce2, 0x008ed2, 0x009063, 0x009375, + 0x00967a, 0x009855, 0x009a13, 0x009e78, 0x005143, 0x00539f, 0x0053b3, 0x005e7b, + 0x005f26, 0x006e1b, 0x006e90, 0x007384, 0x0073fe, 0x007d43, 0x008237, 0x008a00, + 0x008afa, 0x009650, 0x004e4e, 0x00500b, 0x0053e4, 0x00547c, 0x0056fa, 0x0059d1, + 0x005b64, 0x005df1, 0x005eab, 0x005f27, 0x006238, 0x006545, 0x0067af, 0x006e56, + 0x0072d0, 0x007cca, 0x0088b4, 0x0080a1, 0x0080e1, 0x0083f0, 0x00864e, 0x008a87, + 0x008de8, 0x009237, 0x0096c7, 0x009867, 0x009f13, 0x004e94, 0x004e92, 0x004f0d, + 0x005348, 0x005449, 0x00543e, 0x005a2f, 0x005f8c, 0x005fa1, 0x00609f, 0x0068a7, + 0x006a8e, 0x00745a, 0x007881, 0x008a9e, 0x008aa4, 0x008b77, 0x009190, 0x004e5e, + 0x009bc9, 0x004ea4, 0x004f7c, 0x004faf, 0x005019, 0x005016, 0x005149, 0x00516c, + 0x00529f, 0x0052b9, 0x0052fe, 0x00539a, 0x0053e3, 0x005411, + // plane 1 row 25 + 0x00540e, 0x005589, 0x005751, 0x0057a2, 0x00597d, 0x005b54, 0x005b5d, 0x005b8f, + 0x005de5, 0x005de7, 0x005df7, 0x005e78, 0x005e83, 0x005e9a, 0x005eb7, 0x005f18, + 0x006052, 0x00614c, 0x006297, 0x0062d8, 0x0063a7, 0x00653b, 0x006602, 0x006643, + 0x0066f4, 0x00676d, 0x006821, 0x006897, 0x0069cb, 0x006c5f, 0x006d2a, 0x006d69, + 0x006e2f, 0x006e9d, 0x007532, 0x007687, 0x00786c, 0x007a3f, 0x007ce0, 0x007d05, + 0x007d18, 0x007d5e, 0x007db1, 0x008015, 0x008003, 0x0080af, 0x0080b1, 0x008154, + 0x00818f, 0x00822a, 0x008352, 0x00884c, 0x008861, 0x008b1b, 0x008ca2, 0x008cfc, + 0x0090ca, 0x009175, 0x009271, 0x00783f, 0x0092fc, 0x0095a4, 0x00964d, 0x009805, + 0x009999, 0x009ad8, 0x009d3b, 0x00525b, 0x0052ab, 0x0053f7, 0x005408, 0x0058d5, + 0x0062f7, 0x006fe0, 0x008c6a, 0x008f5f, 0x009eb9, 0x00514b, 0x00523b, 0x00544a, + 0x0056fd, 0x007a40, 0x009177, 0x009d60, 0x009ed2, 0x007344, 0x006f09, 0x008170, + 0x007511, 0x005ffd, 0x0060da, 0x009aa8, 0x0072db, 0x008fbc, + // plane 1 row 26 + 0x006b64, 0x009803, 0x004eca, 0x0056f0, 0x005764, 0x0058be, 0x005a5a, 0x006068, + 0x0061c7, 0x00660f, 0x006606, 0x006839, 0x0068b1, 0x006df7, 0x0075d5, 0x007d3a, + 0x00826e, 0x009b42, 0x004e9b, 0x004f50, 0x0053c9, 0x005506, 0x005d6f, 0x005de6, + 0x005dee, 0x0067fb, 0x006c99, 0x007473, 0x007802, 0x008a50, 0x009396, 0x0088df, + 0x005750, 0x005ea7, 0x00632b, 0x0050b5, 0x0050ac, 0x00518d, 0x006700, 0x0054c9, + 0x00585e, 0x0059bb, 0x005bb0, 0x005f69, 0x00624d, 0x0063a1, 0x00683d, 0x006b73, + 0x006e08, 0x00707d, 0x0091c7, 0x007280, 0x007815, 0x007826, 0x00796d, 0x00658e, + 0x007d30, 0x0083dc, 0x0088c1, 0x008f09, 0x00969b, 0x005264, 0x005728, 0x006750, + 0x007f6a, 0x008ca1, 0x0051b4, 0x005742, 0x00962a, 0x00583a, 0x00698a, 0x0080b4, + 0x0054b2, 0x005d0e, 0x0057fc, 0x007895, 0x009dfa, 0x004f5c, 0x00524a, 0x00548b, + 0x00643e, 0x006628, 0x006714, 0x0067f5, 0x007a84, 0x007b56, 0x007d22, 0x00932f, + 0x00685c, 0x009bad, 0x007b39, 0x005319, 0x00518a, 0x005237, + // plane 1 row 27 + 0x005bdf, 0x0062f6, 0x0064ae, 0x0064e6, 0x00672d, 0x006bba, 0x0085a9, 0x0096d1, + 0x007690, 0x009bd6, 0x00634c, 0x009306, 0x009bab, 0x0076bf, 0x006652, 0x004e09, + 0x005098, 0x0053c2, 0x005c71, 0x0060e8, 0x006492, 0x006563, 0x00685f, 0x0071e6, + 0x0073ca, 0x007523, 0x007b97, 0x007e82, 0x008695, 0x008b83, 0x008cdb, 0x009178, + 0x009910, 0x0065ac, 0x0066ab, 0x006b8b, 0x004ed5, 0x004ed4, 0x004f3a, 0x004f7f, + 0x00523a, 0x0053f8, 0x0053f2, 0x0055e3, 0x0056db, 0x0058eb, 0x0059cb, 0x0059c9, + 0x0059ff, 0x005b50, 0x005c4d, 0x005e02, 0x005e2b, 0x005fd7, 0x00601d, 0x006307, + 0x00652f, 0x005b5c, 0x0065af, 0x0065bd, 0x0065e8, 0x00679d, 0x006b62, 0x006b7b, + 0x006c0f, 0x007345, 0x007949, 0x0079c1, 0x007cf8, 0x007d19, 0x007d2b, 0x0080a2, + 0x008102, 0x0081f3, 0x008996, 0x008a5e, 0x008a69, 0x008a66, 0x008a8c, 0x008aee, + 0x008cc7, 0x008cdc, 0x0096cc, 0x0098fc, 0x006b6f, 0x004e8b, 0x004f3c, 0x004f8d, + 0x005150, 0x005b57, 0x005bfa, 0x006148, 0x006301, 0x006642, + // plane 1 row 28 + 0x006b21, 0x006ecb, 0x006cbb, 0x00723e, 0x0074bd, 0x0075d4, 0x0078c1, 0x00793a, + 0x00800c, 0x008033, 0x0081ea, 0x008494, 0x008f9e, 0x006c50, 0x009e7f, 0x005f0f, + 0x008b58, 0x009d2b, 0x007afa, 0x008ef8, 0x005b8d, 0x0096eb, 0x004e03, 0x0053f1, + 0x0057f7, 0x005931, 0x005ac9, 0x005ba4, 0x006089, 0x006e7f, 0x006f06, 0x0075be, + 0x008cea, 0x005b9f, 0x008500, 0x007be0, 0x005072, 0x0067f4, 0x00829d, 0x005c61, + 0x00854a, 0x007e1e, 0x00820e, 0x005199, 0x005c04, 0x006368, 0x008d66, 0x00659c, + 0x00716e, 0x00793e, 0x007d17, 0x008005, 0x008b1d, 0x008eca, 0x00906e, 0x0086c7, + 0x0090aa, 0x00501f, 0x0052fa, 0x005c3a, 0x006753, 0x00707c, 0x007235, 0x00914c, + 0x0091c8, 0x00932b, 0x0082e5, 0x005bc2, 0x005f31, 0x0060f9, 0x004e3b, 0x0053d6, + 0x005b88, 0x00624b, 0x006731, 0x006b8a, 0x0072e9, 0x0073e0, 0x007a2e, 0x00816b, + 0x008da3, 0x009152, 0x009996, 0x005112, 0x0053d7, 0x00546a, 0x005bff, 0x006388, + 0x006a39, 0x007dac, 0x009700, 0x0056da, 0x0053ce, 0x005468, + // plane 1 row 29 + 0x005b97, 0x005c31, 0x005dde, 0x004fee, 0x006101, 0x0062fe, 0x006d32, 0x0079c0, + 0x0079cb, 0x007d42, 0x007e4d, 0x007fd2, 0x0081ed, 0x00821f, 0x008490, 0x008846, + 0x008972, 0x008b90, 0x008e74, 0x008f2f, 0x009031, 0x00914b, 0x00916c, 0x0096c6, + 0x00919c, 0x004ec0, 0x004f4f, 0x005145, 0x005341, 0x005f93, 0x00620e, 0x0067d4, + 0x006c41, 0x006e0b, 0x007363, 0x007e26, 0x0091cd, 0x009283, 0x0053d4, 0x005919, + 0x005bbf, 0x006dd1, 0x00795d, 0x007e2e, 0x007c9b, 0x00587e, 0x00719f, 0x0051fa, + 0x008853, 0x008ff0, 0x004fca, 0x005cfb, 0x006625, 0x0077ac, 0x007ae3, 0x00821c, + 0x0099ff, 0x0051c6, 0x005faa, 0x0065ec, 0x00696f, 0x006b89, 0x006df3, 0x006e96, + 0x006f64, 0x0076fe, 0x007d14, 0x005de1, 0x009075, 0x009187, 0x009806, 0x0051e6, + 0x00521d, 0x006240, 0x006691, 0x0066d9, 0x006e1a, 0x005eb6, 0x007dd2, 0x007f72, + 0x0066f8, 0x0085af, 0x0085f7, 0x008af8, 0x0052a9, 0x0053d9, 0x005973, 0x005e8f, + 0x005f90, 0x006055, 0x0092e4, 0x009664, 0x0050b7, 0x00511f, + // plane 1 row 30 + 0x0052dd, 0x005320, 0x005347, 0x0053ec, 0x0054e8, 0x005546, 0x005531, 0x005617, + 0x005968, 0x0059be, 0x005a3c, 0x005bb5, 0x005c06, 0x005c0f, 0x005c11, 0x005c1a, + 0x005e84, 0x005e8a, 0x005ee0, 0x005f70, 0x00627f, 0x006284, 0x0062db, 0x00638c, + 0x006377, 0x006607, 0x00660c, 0x00662d, 0x006676, 0x00677e, 0x0068a2, 0x006a1f, + 0x006a35, 0x006cbc, 0x006d88, 0x006e09, 0x006e58, 0x00713c, 0x007126, 0x007167, + 0x0075c7, 0x007701, 0x00785d, 0x007901, 0x007965, 0x0079f0, 0x007ae0, 0x007b11, + 0x007ca7, 0x007d39, 0x008096, 0x0083d6, 0x00848b, 0x008549, 0x00885d, 0x0088f3, + 0x008a1f, 0x008a3c, 0x008a54, 0x008a73, 0x008c61, 0x008cde, 0x0091a4, 0x009266, + 0x00937e, 0x009418, 0x00969c, 0x009798, 0x004e0a, 0x004e08, 0x004e1e, 0x004e57, + 0x005197, 0x005270, 0x0057ce, 0x005834, 0x0058cc, 0x005b22, 0x005e38, 0x0060c5, + 0x0064fe, 0x006761, 0x006756, 0x006d44, 0x0072b6, 0x007573, 0x007a63, 0x0084b8, + 0x008b72, 0x0091b8, 0x009320, 0x005631, 0x0057f4, 0x0098fe, + // plane 1 row 31 + 0x0062ed, 0x00690d, 0x006b96, 0x0071ed, 0x007e54, 0x008077, 0x008272, 0x0089e6, + 0x0098df, 0x008755, 0x008fb1, 0x005c3b, 0x004f38, 0x004fe1, 0x004fb5, 0x005507, + 0x005a20, 0x005bdd, 0x005be9, 0x005fc3, 0x00614e, 0x00632f, 0x0065b0, 0x00664b, + 0x0068ee, 0x00699b, 0x006d78, 0x006df1, 0x007533, 0x0075b9, 0x00771f, 0x00795e, + 0x0079e6, 0x007d33, 0x0081e3, 0x0082af, 0x0085aa, 0x0089aa, 0x008a3a, 0x008eab, + 0x008f9b, 0x009032, 0x0091dd, 0x009707, 0x004eba, 0x004ec1, 0x005203, 0x005875, + 0x0058ec, 0x005c0b, 0x00751a, 0x005c3d, 0x00814e, 0x008a0a, 0x008fc5, 0x009663, + 0x00976d, 0x007b25, 0x008acf, 0x009808, 0x009162, 0x0056f3, 0x0053a8, 0x009017, + 0x005439, 0x005782, 0x005e25, 0x0063a8, 0x006c34, 0x00708a, 0x007761, 0x007c8b, + 0x007fe0, 0x008870, 0x009042, 0x009154, 0x009310, 0x009318, 0x00968f, 0x00745e, + 0x009ac4, 0x005d07, 0x005d69, 0x006570, 0x0067a2, 0x008da8, 0x0096db, 0x00636e, + 0x006749, 0x006919, 0x0083c5, 0x009817, 0x0096c0, 0x0088fe, + // plane 1 row 32 + 0x006f84, 0x00647a, 0x005bf8, 0x004e16, 0x00702c, 0x00755d, 0x00662f, 0x0051c4, + 0x005236, 0x0052e2, 0x0059d3, 0x005f81, 0x006027, 0x006210, 0x00653f, 0x006574, + 0x00661f, 0x006674, 0x0068f2, 0x006816, 0x006b63, 0x006e05, 0x007272, 0x00751f, + 0x0076db, 0x007cbe, 0x008056, 0x0058f0, 0x0088fd, 0x00897f, 0x008aa0, 0x008a93, + 0x008acb, 0x00901d, 0x009192, 0x009752, 0x009759, 0x006589, 0x007a0e, 0x008106, + 0x0096bb, 0x005e2d, 0x0060dc, 0x00621a, 0x0065a5, 0x006614, 0x006790, 0x0077f3, + 0x007a4d, 0x007c4d, 0x007e3e, 0x00810a, 0x008cac, 0x008d64, 0x008de1, 0x008e5f, + 0x0078a9, 0x005207, 0x0062d9, 0x0063a5, 0x006442, 0x006298, 0x008a2d, 0x007a83, + 0x007bc0, 0x008aac, 0x0096ea, 0x007d76, 0x00820c, 0x008749, 0x004ed9, 0x005148, + 0x005343, 0x005360, 0x005ba3, 0x005c02, 0x005c16, 0x005ddd, 0x006226, 0x006247, + 0x0064b0, 0x006813, 0x006834, 0x006cc9, 0x006d45, 0x006d17, 0x0067d3, 0x006f5c, + 0x00714e, 0x00717d, 0x0065cb, 0x007a7f, 0x007bad, 0x007dda, + // plane 1 row 33 + 0x007e4a, 0x007fa8, 0x00817a, 0x00821b, 0x008239, 0x0085a6, 0x008a6e, 0x008cce, + 0x008df5, 0x009078, 0x009077, 0x0092ad, 0x009291, 0x009583, 0x009bae, 0x00524d, + 0x005584, 0x006f38, 0x007136, 0x005168, 0x007985, 0x007e55, 0x0081b3, 0x007cce, + 0x00564c, 0x005851, 0x005ca8, 0x0063aa, 0x0066fe, 0x0066fd, 0x00695a, 0x0072d9, + 0x00758f, 0x00758e, 0x00790e, 0x007956, 0x0079df, 0x007c97, 0x007d20, 0x007d44, + 0x008607, 0x008a34, 0x00963b, 0x009061, 0x009f20, 0x0050e7, 0x005275, 0x0053cc, + 0x0053e2, 0x005009, 0x0055aa, 0x0058ee, 0x00594f, 0x00723d, 0x005b8b, 0x005c64, + 0x00531d, 0x0060e3, 0x0060f3, 0x00635c, 0x006383, 0x00633f, 0x0063bb, 0x0064cd, + 0x0065e9, 0x0066f9, 0x005de3, 0x0069cd, 0x0069fd, 0x006f15, 0x0071e5, 0x004e89, + 0x0075e9, 0x0076f8, 0x007a93, 0x007cdf, 0x007dcf, 0x007d9c, 0x008061, 0x008349, + 0x008358, 0x00846c, 0x0084bc, 0x0085fb, 0x0088c5, 0x008d70, 0x009001, 0x00906d, + 0x009397, 0x00971c, 0x009a12, 0x0050cf, 0x005897, 0x00618e, + // plane 1 row 34 + 0x0081d3, 0x008535, 0x008d08, 0x009020, 0x004fc3, 0x005074, 0x005247, 0x005373, + 0x00606f, 0x006349, 0x00675f, 0x006e2c, 0x008db3, 0x00901f, 0x004fd7, 0x005c5e, + 0x008cca, 0x0065cf, 0x007d9a, 0x005352, 0x008896, 0x005176, 0x0063c3, 0x005b58, + 0x005b6b, 0x005c0a, 0x00640d, 0x006751, 0x00905c, 0x004ed6, 0x00591a, 0x00592a, + 0x006c70, 0x008a51, 0x00553e, 0x005815, 0x0059a5, 0x0060f0, 0x006253, 0x0067c1, + 0x008235, 0x006955, 0x009640, 0x0099c4, 0x009a28, 0x004f53, 0x005806, 0x005bfe, + 0x008010, 0x005cb1, 0x005e2f, 0x005f85, 0x006020, 0x00614b, 0x006234, 0x0066ff, + 0x006cf0, 0x006ede, 0x0080ce, 0x00817f, 0x0082d4, 0x00888b, 0x008cb8, 0x009000, + 0x00902e, 0x00968a, 0x009edb, 0x009bdb, 0x004ee3, 0x0053f0, 0x005927, 0x007b2c, + 0x00918d, 0x00984c, 0x009df9, 0x006edd, 0x007027, 0x005353, 0x005544, 0x005b85, + 0x006258, 0x00629e, 0x0062d3, 0x006ca2, 0x006fef, 0x007422, 0x008a17, 0x009438, + 0x006fc1, 0x008afe, 0x008338, 0x0051e7, 0x0086f8, 0x0053ea, + // plane 1 row 35 + 0x0053e9, 0x004f46, 0x009054, 0x008fb0, 0x00596a, 0x008131, 0x005dfd, 0x007aea, + 0x008fbf, 0x0068da, 0x008c37, 0x0072f8, 0x009c48, 0x006a3d, 0x008ab0, 0x004e39, + 0x005358, 0x005606, 0x005766, 0x0062c5, 0x0063a2, 0x0065e6, 0x006b4e, 0x006de1, + 0x006e5b, 0x0070ad, 0x0077ed, 0x007aef, 0x007baa, 0x007dbb, 0x00803d, 0x0080c6, + 0x0086cb, 0x008a95, 0x00935b, 0x0056e3, 0x0058c7, 0x005f3e, 0x0065ad, 0x006696, + 0x006a80, 0x006bb5, 0x007537, 0x008ac7, 0x005024, 0x0077e5, 0x005730, 0x005f1b, + 0x006065, 0x00667a, 0x006c60, 0x0075f4, 0x007a1a, 0x007f6e, 0x0081f4, 0x008718, + 0x009045, 0x0099b3, 0x007bc9, 0x00755c, 0x007af9, 0x007b51, 0x0084c4, 0x009010, + 0x0079e9, 0x007a92, 0x008336, 0x005ae1, 0x007740, 0x004e2d, 0x004ef2, 0x005b99, + 0x005fe0, 0x0062bd, 0x00663c, 0x0067f1, 0x006ce8, 0x00866b, 0x008877, 0x008a3b, + 0x00914e, 0x0092f3, 0x0099d0, 0x006a17, 0x007026, 0x00732a, 0x0082e7, 0x008457, + 0x008caf, 0x004e01, 0x005146, 0x0051cb, 0x00558b, 0x005bf5, + // plane 1 row 36 + 0x005e16, 0x005e33, 0x005e81, 0x005f14, 0x005f35, 0x005f6b, 0x005fb4, 0x0061f2, + 0x006311, 0x0066a2, 0x00671d, 0x006f6e, 0x007252, 0x00753a, 0x00773a, 0x008074, + 0x008139, 0x008178, 0x008776, 0x008abf, 0x008adc, 0x008d85, 0x008df3, 0x00929a, + 0x009577, 0x009802, 0x009ce5, 0x0052c5, 0x006357, 0x0076f4, 0x006715, 0x006c88, + 0x0073cd, 0x008cc3, 0x0093ae, 0x009673, 0x006d25, 0x00589c, 0x00690e, 0x0069cc, + 0x008ffd, 0x00939a, 0x0075db, 0x00901a, 0x00585a, 0x006802, 0x0063b4, 0x0069fb, + 0x004f43, 0x006f2c, 0x0067d8, 0x008fbb, 0x008526, 0x007db4, 0x009354, 0x00693f, + 0x006f70, 0x00576a, 0x0058f7, 0x005b2c, 0x007d2c, 0x00722a, 0x00540a, 0x0091e3, + 0x009db4, 0x004ead, 0x004f4e, 0x00505c, 0x005075, 0x005243, 0x008c9e, 0x005448, + 0x005824, 0x005b9a, 0x005e1d, 0x005e95, 0x005ead, 0x005ef7, 0x005f1f, 0x00608c, + 0x0062b5, 0x00633a, 0x0063d0, 0x0068af, 0x006c40, 0x007887, 0x00798e, 0x007a0b, + 0x007de0, 0x008247, 0x008a02, 0x008ae6, 0x008e44, 0x009013, + // plane 1 row 37 + 0x0090b8, 0x00912d, 0x0091d8, 0x009f0e, 0x006ce5, 0x006458, 0x0064e2, 0x006575, + 0x006ef4, 0x007684, 0x007b1b, 0x009069, 0x0093d1, 0x006eba, 0x0054f2, 0x005fb9, + 0x0064a4, 0x008f4d, 0x008fed, 0x009244, 0x005178, 0x00586b, 0x005929, 0x005c55, + 0x005e97, 0x006dfb, 0x007e8f, 0x00751c, 0x008cbc, 0x008ee2, 0x00985b, 0x0070b9, + 0x004f1d, 0x006bbf, 0x006fb1, 0x007530, 0x0096fb, 0x00514e, 0x005410, 0x005835, + 0x005857, 0x0059ac, 0x005c60, 0x005f92, 0x006597, 0x00675c, 0x006e21, 0x00767b, + 0x0083df, 0x008ced, 0x009014, 0x0090fd, 0x00934d, 0x007825, 0x00783a, 0x0052aa, + 0x005ea6, 0x00571f, 0x005974, 0x006012, 0x005012, 0x00515a, 0x0051ac, 0x0051cd, + 0x005200, 0x005510, 0x005854, 0x005858, 0x005957, 0x005b95, 0x005cf6, 0x005d8b, + 0x0060bc, 0x006295, 0x00642d, 0x006771, 0x006843, 0x0068bc, 0x0068df, 0x0076d7, + 0x006dd8, 0x006e6f, 0x006d9b, 0x00706f, 0x0071c8, 0x005f53, 0x0075d8, 0x007977, + 0x007b49, 0x007b54, 0x007b52, 0x007cd6, 0x007d71, 0x005230, + // plane 1 row 38 + 0x008463, 0x008569, 0x0085e4, 0x008a0e, 0x008b04, 0x008c46, 0x008e0f, 0x009003, + 0x00900f, 0x009419, 0x009676, 0x00982d, 0x009a30, 0x0095d8, 0x0050cd, 0x0052d5, + 0x00540c, 0x005802, 0x005c0e, 0x0061a7, 0x00649e, 0x006d1e, 0x0077b3, 0x007ae5, + 0x0080f4, 0x008404, 0x009053, 0x009285, 0x005ce0, 0x009d07, 0x00533f, 0x005f97, + 0x005fb3, 0x006d9c, 0x007279, 0x007763, 0x0079bf, 0x007be4, 0x006bd2, 0x0072ec, + 0x008aad, 0x006803, 0x006a61, 0x0051f8, 0x007a81, 0x006934, 0x005c4a, 0x009cf6, + 0x0082eb, 0x005bc5, 0x009149, 0x00701e, 0x005678, 0x005c6f, 0x0060c7, 0x006566, + 0x006c8c, 0x008c5a, 0x009041, 0x009813, 0x005451, 0x0066c7, 0x00920d, 0x005948, + 0x0090a3, 0x005185, 0x004e4d, 0x0051ea, 0x008599, 0x008b0e, 0x007058, 0x00637a, + 0x00934b, 0x006962, 0x0099b4, 0x007e04, 0x007577, 0x005357, 0x006960, 0x008edf, + 0x0096e3, 0x006c5d, 0x004e8c, 0x005c3c, 0x005f10, 0x008fe9, 0x005302, 0x008cd1, + 0x008089, 0x008679, 0x005eff, 0x0065e5, 0x004e73, 0x005165, + // plane 1 row 39 + 0x005982, 0x005c3f, 0x0097ee, 0x004efb, 0x00598a, 0x005fcd, 0x008a8d, 0x006fe1, + 0x0079b0, 0x007962, 0x005be7, 0x008471, 0x00732b, 0x0071b1, 0x005e74, 0x005ff5, + 0x00637b, 0x00649a, 0x0071c3, 0x007c98, 0x004e43, 0x005efc, 0x004e4b, 0x0057dc, + 0x0056a2, 0x0060a9, 0x006fc3, 0x007d0d, 0x0080fd, 0x008133, 0x0081bf, 0x008fb2, + 0x008997, 0x0086a4, 0x005df4, 0x00628a, 0x0064ad, 0x008987, 0x006777, 0x006ce2, + 0x006d3e, 0x007436, 0x007834, 0x005a46, 0x007f75, 0x0082ad, 0x0099ac, 0x004ff3, + 0x005ec3, 0x0062dd, 0x006392, 0x006557, 0x00676f, 0x0076c3, 0x00724c, 0x0080cc, + 0x0080ba, 0x008f29, 0x00914d, 0x00500d, 0x0057f9, 0x005a92, 0x006885, 0x006973, + 0x007164, 0x0072fd, 0x008cb7, 0x0058f2, 0x008ce0, 0x00966a, 0x009019, 0x00877f, + 0x0079e4, 0x0077e7, 0x008429, 0x004f2f, 0x005265, 0x00535a, 0x0062cd, 0x0067cf, + 0x006cca, 0x00767d, 0x007b94, 0x007c95, 0x008236, 0x008584, 0x008feb, 0x0066dd, + 0x006f20, 0x007206, 0x007e1b, 0x0083ab, 0x0099c1, 0x009ea6, + // plane 1 row 40 + 0x0051fd, 0x007bb1, 0x007872, 0x007bb8, 0x008087, 0x007b48, 0x006ae8, 0x005e61, + 0x00808c, 0x007551, 0x007560, 0x00516b, 0x009262, 0x006e8c, 0x00767a, 0x009197, + 0x009aea, 0x004f10, 0x007f70, 0x00629c, 0x007b4f, 0x0095a5, 0x009ce9, 0x00567a, + 0x005859, 0x0086e4, 0x0096bc, 0x004f34, 0x005224, 0x00534a, 0x0053cd, 0x0053db, + 0x005e06, 0x00642c, 0x006591, 0x00677f, 0x006c3e, 0x006c4e, 0x007248, 0x0072af, + 0x0073ed, 0x007554, 0x007e41, 0x00822c, 0x0085e9, 0x008ca9, 0x007bc4, 0x0091c6, + 0x007169, 0x009812, 0x0098ef, 0x00633d, 0x006669, 0x00756a, 0x0076e4, 0x0078d0, + 0x008543, 0x0086ee, 0x00532a, 0x005351, 0x005426, 0x005983, 0x005e87, 0x005f7c, + 0x0060b2, 0x006249, 0x006279, 0x0062ab, 0x006590, 0x006bd4, 0x006ccc, 0x0075b2, + 0x0076ae, 0x007891, 0x0079d8, 0x007dcb, 0x007f77, 0x0080a5, 0x0088ab, 0x008ab9, + 0x008cbb, 0x00907f, 0x00975e, 0x0098db, 0x006a0b, 0x007c38, 0x005099, 0x005c3e, + 0x005fae, 0x006787, 0x006bd8, 0x007435, 0x007709, 0x007f8e, + // plane 1 row 41 + 0x009f3b, 0x0067ca, 0x007a17, 0x005339, 0x00758b, 0x009aed, 0x005f66, 0x00819d, + 0x0083f1, 0x008098, 0x005f3c, 0x005fc5, 0x007562, 0x007b46, 0x00903c, 0x006867, + 0x0059eb, 0x005a9b, 0x007d10, 0x00767e, 0x008b2c, 0x004ff5, 0x005f6a, 0x006a19, + 0x006c37, 0x006f02, 0x0074e2, 0x007968, 0x008868, 0x008a55, 0x008c79, 0x005edf, + 0x0063cf, 0x0075c5, 0x0079d2, 0x0082d7, 0x009328, 0x0092f2, 0x00849c, 0x0086ed, + 0x009c2d, 0x0054c1, 0x005f6c, 0x00658c, 0x006d5c, 0x007015, 0x008ca7, 0x008cd3, + 0x00983b, 0x00654f, 0x0074f6, 0x004e0d, 0x004ed8, 0x0057e0, 0x00592b, 0x005a66, + 0x005bcc, 0x0051a8, 0x005e03, 0x005e9c, 0x006016, 0x006276, 0x006577, 0x0065a7, + 0x00666e, 0x006d6e, 0x007236, 0x007b26, 0x008150, 0x00819a, 0x008299, 0x008b5c, + 0x008ca0, 0x008ce6, 0x008d74, 0x00961c, 0x009644, 0x004fae, 0x0064ab, 0x006b66, + 0x00821e, 0x008461, 0x00856a, 0x0090e8, 0x005c01, 0x006953, 0x0098a8, 0x00847a, + 0x008557, 0x004f0f, 0x00526f, 0x005fa9, 0x005e45, 0x00670d, + // plane 1 row 42 + 0x00798f, 0x008179, 0x008907, 0x008986, 0x006df5, 0x005f17, 0x006255, 0x006cb8, + 0x004ecf, 0x007269, 0x009b92, 0x005206, 0x00543b, 0x005674, 0x0058b3, 0x0061a4, + 0x00626e, 0x00711a, 0x00596e, 0x007c89, 0x007cde, 0x007d1b, 0x0096f0, 0x006587, + 0x00805e, 0x004e19, 0x004f75, 0x005175, 0x005840, 0x005e63, 0x005e73, 0x005f0a, + 0x0067c4, 0x004e26, 0x00853d, 0x009589, 0x00965b, 0x007c73, 0x009801, 0x0050fb, + 0x0058c1, 0x007656, 0x0078a7, 0x005225, 0x0077a5, 0x008511, 0x007b86, 0x00504f, + 0x005909, 0x007247, 0x007bc7, 0x007de8, 0x008fba, 0x008fd4, 0x00904d, 0x004fbf, + 0x0052c9, 0x005a29, 0x005f01, 0x0097ad, 0x004fdd, 0x008217, 0x0092ea, 0x005703, + 0x006355, 0x006b69, 0x00752b, 0x0088dc, 0x008f14, 0x007a42, 0x0052df, 0x005893, + 0x006155, 0x00620a, 0x0066ae, 0x006bcd, 0x007c3f, 0x0083e9, 0x005023, 0x004ff8, + 0x005305, 0x005446, 0x005831, 0x005949, 0x005b9d, 0x005cf0, 0x005cef, 0x005d29, + 0x005e96, 0x0062b1, 0x006367, 0x00653e, 0x0065b9, 0x00670b, + // plane 1 row 43 + 0x006cd5, 0x006ce1, 0x0070f9, 0x007832, 0x007e2b, 0x0080de, 0x0082b3, 0x00840c, + 0x0084ec, 0x008702, 0x008912, 0x008a2a, 0x008c4a, 0x0090a6, 0x0092d2, 0x0098fd, + 0x009cf3, 0x009d6c, 0x004e4f, 0x004ea1, 0x00508d, 0x005256, 0x00574a, 0x0059a8, + 0x005e3d, 0x005fd8, 0x005fd9, 0x00623f, 0x0066b4, 0x00671b, 0x0067d0, 0x0068d2, + 0x005192, 0x007d21, 0x0080aa, 0x0081a8, 0x008b00, 0x008c8c, 0x008cbf, 0x00927e, + 0x009632, 0x005420, 0x00982c, 0x005317, 0x0050d5, 0x00535c, 0x0058a8, 0x0064b2, + 0x006734, 0x007267, 0x007766, 0x007a46, 0x0091e6, 0x0052c3, 0x006ca1, 0x006b86, + 0x005800, 0x005e4c, 0x005954, 0x00672c, 0x007ffb, 0x0051e1, 0x0076c6, 0x006469, + 0x0078e8, 0x009b54, 0x009ebb, 0x0057cb, 0x0059b9, 0x006627, 0x00679a, 0x006bce, + 0x0054e9, 0x0069d9, 0x005e55, 0x00819c, 0x006795, 0x009baa, 0x0067fe, 0x009c52, + 0x00685d, 0x004ea6, 0x004fe3, 0x0053c8, 0x0062b9, 0x00672b, 0x006cab, 0x008fc4, + 0x004fad, 0x007e6d, 0x009ebf, 0x004e07, 0x006162, 0x006e80, + // plane 1 row 44 + 0x006f2b, 0x008513, 0x005473, 0x00672a, 0x009b45, 0x005df3, 0x007b95, 0x005cac, + 0x005bc6, 0x00871c, 0x006e4a, 0x0084d1, 0x007a14, 0x008108, 0x005999, 0x007c8d, + 0x006c11, 0x007720, 0x0052d9, 0x005922, 0x007121, 0x00725f, 0x0077db, 0x009727, + 0x009d61, 0x00690b, 0x005a7f, 0x005a18, 0x0051a5, 0x00540d, 0x00547d, 0x00660e, + 0x0076df, 0x008ff7, 0x009298, 0x009cf4, 0x0059ea, 0x00725d, 0x006ec5, 0x00514d, + 0x0068c9, 0x007dbf, 0x007dec, 0x009762, 0x009eba, 0x006478, 0x006a21, 0x008302, + 0x005984, 0x005b5f, 0x006bdb, 0x00731b, 0x0076f2, 0x007db2, 0x008017, 0x008499, + 0x005132, 0x006728, 0x009ed9, 0x0076ee, 0x006762, 0x0052ff, 0x009905, 0x005c24, + 0x00623b, 0x007c7e, 0x008cb0, 0x00554f, 0x0060b6, 0x007d0b, 0x009580, 0x005301, + 0x004e5f, 0x0051b6, 0x00591c, 0x00723a, 0x008036, 0x0091ce, 0x005f25, 0x0077e2, + 0x005384, 0x005f79, 0x007d04, 0x0085ac, 0x008a33, 0x008e8d, 0x009756, 0x0067f3, + 0x0085ae, 0x009453, 0x006109, 0x006108, 0x006cb9, 0x007652, + // plane 1 row 45 + 0x008aed, 0x008f38, 0x00552f, 0x004f51, 0x00512a, 0x0052c7, 0x0053cb, 0x005ba5, + 0x005e7d, 0x0060a0, 0x006182, 0x0063d6, 0x006709, 0x0067da, 0x006e67, 0x006d8c, + 0x007336, 0x007337, 0x007531, 0x007950, 0x0088d5, 0x008a98, 0x00904a, 0x009091, + 0x0090f5, 0x0096c4, 0x00878d, 0x005915, 0x004e88, 0x004f59, 0x004e0e, 0x008a89, + 0x008f3f, 0x009810, 0x0050ad, 0x005e7c, 0x005996, 0x005bb9, 0x005eb8, 0x0063da, + 0x0063fa, 0x0064c1, 0x0066dc, 0x00694a, 0x0069d8, 0x006d0b, 0x006eb6, 0x007194, + 0x007528, 0x007aaf, 0x007f8a, 0x008000, 0x008449, 0x0084c9, 0x008981, 0x008b21, + 0x008e0a, 0x009065, 0x00967d, 0x00990a, 0x00617e, 0x006291, 0x006b32, 0x006c83, + 0x006d74, 0x007fcc, 0x007ffc, 0x006dc0, 0x007f85, 0x0087ba, 0x0088f8, 0x006765, + 0x0083b1, 0x00983c, 0x0096f7, 0x006d1b, 0x007d61, 0x00843d, 0x00916a, 0x004e71, + 0x005375, 0x005d50, 0x006b04, 0x006feb, 0x0085cd, 0x00862d, 0x0089a7, 0x005229, + 0x00540f, 0x005c65, 0x00674e, 0x0068a8, 0x007406, 0x007483, + // plane 1 row 46 + 0x0075e2, 0x0088cf, 0x0088e1, 0x0091cc, 0x0096e2, 0x009678, 0x005f8b, 0x007387, + 0x007acb, 0x00844e, 0x0063a0, 0x007565, 0x005289, 0x006d41, 0x006e9c, 0x007409, + 0x007559, 0x00786b, 0x007c92, 0x009686, 0x007adc, 0x009f8d, 0x004fb6, 0x00616e, + 0x0065c5, 0x00865c, 0x004e86, 0x004eae, 0x0050da, 0x004e21, 0x0051cc, 0x005bee, + 0x006599, 0x006881, 0x006dbc, 0x00731f, 0x007642, 0x0077ad, 0x007a1c, 0x007ce7, + 0x00826f, 0x008ad2, 0x00907c, 0x0091cf, 0x009675, 0x009818, 0x00529b, 0x007dd1, + 0x00502b, 0x005398, 0x006797, 0x006dcb, 0x0071d0, 0x007433, 0x0081e8, 0x008f2a, + 0x0096a3, 0x009c57, 0x009e9f, 0x007460, 0x005841, 0x006d99, 0x007d2f, 0x00985e, + 0x004ee4, 0x004f36, 0x004f8b, 0x0051b7, 0x0052b1, 0x005dba, 0x00601c, 0x0073b2, + 0x00793c, 0x0082d3, 0x009234, 0x0096b7, 0x0096f6, 0x00970a, 0x009e97, 0x009f62, + 0x0066a6, 0x006b74, 0x005217, 0x0052a3, 0x0070c8, 0x0088c2, 0x005ec9, 0x00604b, + 0x006190, 0x006f23, 0x007149, 0x007c3e, 0x007df4, 0x00806f, + // plane 1 row 47 + 0x0084ee, 0x009023, 0x00932c, 0x005442, 0x009b6f, 0x006ad3, 0x007089, 0x008cc2, + 0x008def, 0x009732, 0x0052b4, 0x005a41, 0x005eca, 0x005f04, 0x006717, 0x00697c, + 0x006994, 0x006d6a, 0x006f0f, 0x007262, 0x0072fc, 0x007bed, 0x008001, 0x00807e, + 0x00874b, 0x0090ce, 0x00516d, 0x009e93, 0x007984, 0x00808b, 0x009332, 0x008ad6, + 0x00502d, 0x00548c, 0x008a71, 0x006b6a, 0x008cc4, 0x008107, 0x0060d1, 0x0067a0, + 0x009df2, 0x004e99, 0x004e98, 0x009c10, 0x008a6b, 0x0085c1, 0x008568, 0x006900, + 0x006e7e, 0x007897, 0x008155, 0x020b9f, 0x005b41, 0x005b56, 0x005b7d, 0x005b93, + 0x005bd8, 0x005bec, 0x005c12, 0x005c1e, 0x005c23, 0x005c2b, 0x00378d, 0x005c62, + 0x00fa3b, 0x00fa3c, 0x0216b4, 0x005c7a, 0x005c8f, 0x005c9f, 0x005ca3, 0x005caa, + 0x005cba, 0x005ccb, 0x005cd0, 0x005cd2, 0x005cf4, 0x021e34, 0x0037e2, 0x005d0d, + 0x005d27, 0x00fa11, 0x005d46, 0x005d47, 0x005d53, 0x005d4a, 0x005d6d, 0x005d81, + 0x005da0, 0x005da4, 0x005da7, 0x005db8, 0x005dcb, 0x00541e, + // plane 1 row 48 + 0x005f0c, 0x004e10, 0x004e15, 0x004e2a, 0x004e31, 0x004e36, 0x004e3c, 0x004e3f, + 0x004e42, 0x004e56, 0x004e58, 0x004e82, 0x004e85, 0x008c6b, 0x004e8a, 0x008212, + 0x005f0d, 0x004e8e, 0x004e9e, 0x004e9f, 0x004ea0, 0x004ea2, 0x004eb0, 0x004eb3, + 0x004eb6, 0x004ece, 0x004ecd, 0x004ec4, 0x004ec6, 0x004ec2, 0x004ed7, 0x004ede, + 0x004eed, 0x004edf, 0x004ef7, 0x004f09, 0x004f5a, 0x004f30, 0x004f5b, 0x004f5d, + 0x004f57, 0x004f47, 0x004f76, 0x004f88, 0x004f8f, 0x004f98, 0x004f7b, 0x004f69, + 0x004f70, 0x004f91, 0x004f6f, 0x004f86, 0x004f96, 0x005118, 0x004fd4, 0x004fdf, + 0x004fce, 0x004fd8, 0x004fdb, 0x004fd1, 0x004fda, 0x004fd0, 0x004fe4, 0x004fe5, + 0x00501a, 0x005028, 0x005014, 0x00502a, 0x005025, 0x005005, 0x004f1c, 0x004ff6, + 0x005021, 0x005029, 0x00502c, 0x004ffe, 0x004fef, 0x005011, 0x005006, 0x005043, + 0x005047, 0x006703, 0x005055, 0x005050, 0x005048, 0x00505a, 0x005056, 0x00506c, + 0x005078, 0x005080, 0x00509a, 0x005085, 0x0050b4, 0x0050b2, + // plane 1 row 49 + 0x0050c9, 0x0050ca, 0x0050b3, 0x0050c2, 0x0050d6, 0x0050de, 0x0050e5, 0x0050ed, + 0x0050e3, 0x0050ee, 0x0050f9, 0x0050f5, 0x005109, 0x005101, 0x005102, 0x005116, + 0x005115, 0x005114, 0x00511a, 0x005121, 0x00513a, 0x005137, 0x00513c, 0x00513b, + 0x00513f, 0x005140, 0x005152, 0x00514c, 0x005154, 0x005162, 0x007af8, 0x005169, + 0x00516a, 0x00516e, 0x005180, 0x005182, 0x0056d8, 0x00518c, 0x005189, 0x00518f, + 0x005191, 0x005193, 0x005195, 0x005196, 0x0051a4, 0x0051a6, 0x0051a2, 0x0051a9, + 0x0051aa, 0x0051ab, 0x0051b3, 0x0051b1, 0x0051b2, 0x0051b0, 0x0051b5, 0x0051bd, + 0x0051c5, 0x0051c9, 0x0051db, 0x0051e0, 0x008655, 0x0051e9, 0x0051ed, 0x0051f0, + 0x0051f5, 0x0051fe, 0x005204, 0x00520b, 0x005214, 0x00520e, 0x005227, 0x00522a, + 0x00522e, 0x005233, 0x005239, 0x00524f, 0x005244, 0x00524b, 0x00524c, 0x00525e, + 0x005254, 0x00526a, 0x005274, 0x005269, 0x005273, 0x00527f, 0x00527d, 0x00528d, + 0x005294, 0x005292, 0x005271, 0x005288, 0x005291, 0x008fa8, + // plane 1 row 50 + 0x008fa7, 0x0052ac, 0x0052ad, 0x0052bc, 0x0052b5, 0x0052c1, 0x0052cd, 0x0052d7, + 0x0052de, 0x0052e3, 0x0052e6, 0x0098ed, 0x0052e0, 0x0052f3, 0x0052f5, 0x0052f8, + 0x0052f9, 0x005306, 0x005308, 0x007538, 0x00530d, 0x005310, 0x00530f, 0x005315, + 0x00531a, 0x005323, 0x00532f, 0x005331, 0x005333, 0x005338, 0x005340, 0x005346, + 0x005345, 0x004e17, 0x005349, 0x00534d, 0x0051d6, 0x00535e, 0x005369, 0x00536e, + 0x005918, 0x00537b, 0x005377, 0x005382, 0x005396, 0x0053a0, 0x0053a6, 0x0053a5, + 0x0053ae, 0x0053b0, 0x0053b6, 0x0053c3, 0x007c12, 0x0096d9, 0x0053df, 0x0066fc, + 0x0071ee, 0x0053ee, 0x0053e8, 0x0053ed, 0x0053fa, 0x005401, 0x00543d, 0x005440, + 0x00542c, 0x00542d, 0x00543c, 0x00542e, 0x005436, 0x005429, 0x00541d, 0x00544e, + 0x00548f, 0x005475, 0x00548e, 0x00545f, 0x005471, 0x005477, 0x005470, 0x005492, + 0x00547b, 0x005480, 0x005476, 0x005484, 0x005490, 0x005486, 0x0054c7, 0x0054a2, + 0x0054b8, 0x0054a5, 0x0054ac, 0x0054c4, 0x0054c8, 0x0054a8, + // plane 1 row 51 + 0x0054ab, 0x0054c2, 0x0054a4, 0x0054be, 0x0054bc, 0x0054d8, 0x0054e5, 0x0054e6, + 0x00550f, 0x005514, 0x0054fd, 0x0054ee, 0x0054ed, 0x0054fa, 0x0054e2, 0x005539, + 0x005540, 0x005563, 0x00554c, 0x00552e, 0x00555c, 0x005545, 0x005556, 0x005557, + 0x005538, 0x005533, 0x00555d, 0x005599, 0x005580, 0x0054af, 0x00558a, 0x00559f, + 0x00557b, 0x00557e, 0x005598, 0x00559e, 0x0055ae, 0x00557c, 0x005583, 0x0055a9, + 0x005587, 0x0055a8, 0x0055da, 0x0055c5, 0x0055df, 0x0055c4, 0x0055dc, 0x0055e4, + 0x0055d4, 0x005614, 0x0055f7, 0x005616, 0x0055fe, 0x0055fd, 0x00561b, 0x0055f9, + 0x00564e, 0x005650, 0x0071df, 0x005634, 0x005636, 0x005632, 0x005638, 0x00566b, + 0x005664, 0x00562f, 0x00566c, 0x00566a, 0x005686, 0x005680, 0x00568a, 0x0056a0, + 0x005694, 0x00568f, 0x0056a5, 0x0056ae, 0x0056b6, 0x0056b4, 0x0056c2, 0x0056bc, + 0x0056c1, 0x0056c3, 0x0056c0, 0x0056c8, 0x0056ce, 0x0056d1, 0x0056d3, 0x0056d7, + 0x0056ee, 0x0056f9, 0x005700, 0x0056ff, 0x005704, 0x005709, + // plane 1 row 52 + 0x005708, 0x00570b, 0x00570d, 0x005713, 0x005718, 0x005716, 0x0055c7, 0x00571c, + 0x005726, 0x005737, 0x005738, 0x00574e, 0x00573b, 0x005740, 0x00574f, 0x005769, + 0x0057c0, 0x005788, 0x005761, 0x00577f, 0x005789, 0x005793, 0x0057a0, 0x0057b3, + 0x0057a4, 0x0057aa, 0x0057b0, 0x0057c3, 0x0057c6, 0x0057d4, 0x0057d2, 0x0057d3, + 0x00580a, 0x0057d6, 0x0057e3, 0x00580b, 0x005819, 0x00581d, 0x005872, 0x005821, + 0x005862, 0x00584b, 0x005870, 0x006bc0, 0x005852, 0x00583d, 0x005879, 0x005885, + 0x0058b9, 0x00589f, 0x0058ab, 0x0058ba, 0x0058de, 0x0058bb, 0x0058b8, 0x0058ae, + 0x0058c5, 0x0058d3, 0x0058d1, 0x0058d7, 0x0058d9, 0x0058d8, 0x0058e5, 0x0058dc, + 0x0058e4, 0x0058df, 0x0058ef, 0x0058fa, 0x0058f9, 0x0058fb, 0x0058fc, 0x0058fd, + 0x005902, 0x00590a, 0x005910, 0x00591b, 0x0068a6, 0x005925, 0x00592c, 0x00592d, + 0x005932, 0x005938, 0x00593e, 0x007ad2, 0x005955, 0x005950, 0x00594e, 0x00595a, + 0x005958, 0x005962, 0x005960, 0x005967, 0x00596c, 0x005969, + // plane 1 row 53 + 0x005978, 0x005981, 0x00599d, 0x004f5e, 0x004fab, 0x0059a3, 0x0059b2, 0x0059c6, + 0x0059e8, 0x0059dc, 0x00598d, 0x0059d9, 0x0059da, 0x005a25, 0x005a1f, 0x005a11, + 0x005a1c, 0x005a09, 0x005a1a, 0x005a40, 0x005a6c, 0x005a49, 0x005a35, 0x005a36, + 0x005a62, 0x005a6a, 0x005a9a, 0x005abc, 0x005abe, 0x005acb, 0x005ac2, 0x005abd, + 0x005ae3, 0x005ad7, 0x005ae6, 0x005ae9, 0x005ad6, 0x005afa, 0x005afb, 0x005b0c, + 0x005b0b, 0x005b16, 0x005b32, 0x005ad0, 0x005b2a, 0x005b36, 0x005b3e, 0x005b43, + 0x005b45, 0x005b40, 0x005b51, 0x005b55, 0x005b5a, 0x005b5b, 0x005b65, 0x005b69, + 0x005b70, 0x005b73, 0x005b75, 0x005b78, 0x006588, 0x005b7a, 0x005b80, 0x005b83, + 0x005ba6, 0x005bb8, 0x005bc3, 0x005bc7, 0x005bc9, 0x005bd4, 0x005bd0, 0x005be4, + 0x005be6, 0x005be2, 0x005bde, 0x005be5, 0x005beb, 0x005bf0, 0x005bf6, 0x005bf3, + 0x005c05, 0x005c07, 0x005c08, 0x005c0d, 0x005c13, 0x005c20, 0x005c22, 0x005c28, + 0x005c38, 0x005c39, 0x005c41, 0x005c46, 0x005c4e, 0x005c53, + // plane 1 row 54 + 0x005c50, 0x005c4f, 0x005b71, 0x005c6c, 0x005c6e, 0x004e62, 0x005c76, 0x005c79, + 0x005c8c, 0x005c91, 0x005c94, 0x00599b, 0x005cab, 0x005cbb, 0x005cb6, 0x005cbc, + 0x005cb7, 0x005cc5, 0x005cbe, 0x005cc7, 0x005cd9, 0x005ce9, 0x005cfd, 0x005cfa, + 0x005ced, 0x005d8c, 0x005cea, 0x005d0b, 0x005d15, 0x005d17, 0x005d5c, 0x005d1f, + 0x005d1b, 0x005d11, 0x005d14, 0x005d22, 0x005d1a, 0x005d19, 0x005d18, 0x005d4c, + 0x005d52, 0x005d4e, 0x005d4b, 0x005d6c, 0x005d73, 0x005d76, 0x005d87, 0x005d84, + 0x005d82, 0x005da2, 0x005d9d, 0x005dac, 0x005dae, 0x005dbd, 0x005d90, 0x005db7, + 0x005dbc, 0x005dc9, 0x005dcd, 0x005dd3, 0x005dd2, 0x005dd6, 0x005ddb, 0x005deb, + 0x005df2, 0x005df5, 0x005e0b, 0x005e1a, 0x005e19, 0x005e11, 0x005e1b, 0x005e36, + 0x005e37, 0x005e44, 0x005e43, 0x005e40, 0x005e4e, 0x005e57, 0x005e54, 0x005e5f, + 0x005e62, 0x005e64, 0x005e47, 0x005e75, 0x005e76, 0x005e7a, 0x009ebc, 0x005e7f, + 0x005ea0, 0x005ec1, 0x005ec2, 0x005ec8, 0x005ed0, 0x005ecf, + // plane 1 row 55 + 0x005ed6, 0x005ee3, 0x005edd, 0x005eda, 0x005edb, 0x005ee2, 0x005ee1, 0x005ee8, + 0x005ee9, 0x005eec, 0x005ef1, 0x005ef3, 0x005ef0, 0x005ef4, 0x005ef8, 0x005efe, + 0x005f03, 0x005f09, 0x005f5d, 0x005f5c, 0x005f0b, 0x005f11, 0x005f16, 0x005f29, + 0x005f2d, 0x005f38, 0x005f41, 0x005f48, 0x005f4c, 0x005f4e, 0x005f2f, 0x005f51, + 0x005f56, 0x005f57, 0x005f59, 0x005f61, 0x005f6d, 0x005f73, 0x005f77, 0x005f83, + 0x005f82, 0x005f7f, 0x005f8a, 0x005f88, 0x005f91, 0x005f87, 0x005f9e, 0x005f99, + 0x005f98, 0x005fa0, 0x005fa8, 0x005fad, 0x005fbc, 0x005fd6, 0x005ffb, 0x005fe4, + 0x005ff8, 0x005ff1, 0x005fdd, 0x0060b3, 0x005fff, 0x006021, 0x006060, 0x006019, + 0x006010, 0x006029, 0x00600e, 0x006031, 0x00601b, 0x006015, 0x00602b, 0x006026, + 0x00600f, 0x00603a, 0x00605a, 0x006041, 0x00606a, 0x006077, 0x00605f, 0x00604a, + 0x006046, 0x00604d, 0x006063, 0x006043, 0x006064, 0x006042, 0x00606c, 0x00606b, + 0x006059, 0x006081, 0x00608d, 0x0060e7, 0x006083, 0x00609a, + // plane 1 row 56 + 0x006084, 0x00609b, 0x006096, 0x006097, 0x006092, 0x0060a7, 0x00608b, 0x0060e1, + 0x0060b8, 0x0060e0, 0x0060d3, 0x0060b4, 0x005ff0, 0x0060bd, 0x0060c6, 0x0060b5, + 0x0060d8, 0x00614d, 0x006115, 0x006106, 0x0060f6, 0x0060f7, 0x006100, 0x0060f4, + 0x0060fa, 0x006103, 0x006121, 0x0060fb, 0x0060f1, 0x00610d, 0x00610e, 0x006147, + 0x00613e, 0x006128, 0x006127, 0x00614a, 0x00613f, 0x00613c, 0x00612c, 0x006134, + 0x00613d, 0x006142, 0x006144, 0x006173, 0x006177, 0x006158, 0x006159, 0x00615a, + 0x00616b, 0x006174, 0x00616f, 0x006165, 0x006171, 0x00615f, 0x00615d, 0x006153, + 0x006175, 0x006199, 0x006196, 0x006187, 0x0061ac, 0x006194, 0x00619a, 0x00618a, + 0x006191, 0x0061ab, 0x0061ae, 0x0061cc, 0x0061ca, 0x0061c9, 0x0061f7, 0x0061c8, + 0x0061c3, 0x0061c6, 0x0061ba, 0x0061cb, 0x007f79, 0x0061cd, 0x0061e6, 0x0061e3, + 0x0061f6, 0x0061fa, 0x0061f4, 0x0061ff, 0x0061fd, 0x0061fc, 0x0061fe, 0x006200, + 0x006208, 0x006209, 0x00620d, 0x00620c, 0x006214, 0x00621b, + // plane 1 row 57 + 0x00621e, 0x006221, 0x00622a, 0x00622e, 0x006230, 0x006232, 0x006233, 0x006241, + 0x00624e, 0x00625e, 0x006263, 0x00625b, 0x006260, 0x006268, 0x00627c, 0x006282, + 0x006289, 0x00627e, 0x006292, 0x006293, 0x006296, 0x0062d4, 0x006283, 0x006294, + 0x0062d7, 0x0062d1, 0x0062bb, 0x0062cf, 0x0062ff, 0x0062c6, 0x0064d4, 0x0062c8, + 0x0062dc, 0x0062cc, 0x0062ca, 0x0062c2, 0x0062c7, 0x00629b, 0x0062c9, 0x00630c, + 0x0062ee, 0x0062f1, 0x006327, 0x006302, 0x006308, 0x0062ef, 0x0062f5, 0x006350, + 0x00633e, 0x00634d, 0x00641c, 0x00634f, 0x006396, 0x00638e, 0x006380, 0x0063ab, + 0x006376, 0x0063a3, 0x00638f, 0x006389, 0x00639f, 0x0063b5, 0x00636b, 0x006369, + 0x0063be, 0x0063e9, 0x0063c0, 0x0063c6, 0x0063e3, 0x0063c9, 0x0063d2, 0x0063f6, + 0x0063c4, 0x006416, 0x006434, 0x006406, 0x006413, 0x006426, 0x006436, 0x00651d, + 0x006417, 0x006428, 0x00640f, 0x006467, 0x00646f, 0x006476, 0x00644e, 0x00652a, + 0x006495, 0x006493, 0x0064a5, 0x0064a9, 0x006488, 0x0064bc, + // plane 1 row 58 + 0x0064da, 0x0064d2, 0x0064c5, 0x0064c7, 0x0064bb, 0x0064d8, 0x0064c2, 0x0064f1, + 0x0064e7, 0x008209, 0x0064e0, 0x0064e1, 0x0062ac, 0x0064e3, 0x0064ef, 0x00652c, + 0x0064f6, 0x0064f4, 0x0064f2, 0x0064fa, 0x006500, 0x0064fd, 0x006518, 0x00651c, + 0x006505, 0x006524, 0x006523, 0x00652b, 0x006534, 0x006535, 0x006537, 0x006536, + 0x006538, 0x00754b, 0x006548, 0x006556, 0x006555, 0x00654d, 0x006558, 0x00655e, + 0x00655d, 0x006572, 0x006578, 0x006582, 0x006583, 0x008b8a, 0x00659b, 0x00659f, + 0x0065ab, 0x0065b7, 0x0065c3, 0x0065c6, 0x0065c1, 0x0065c4, 0x0065cc, 0x0065d2, + 0x0065db, 0x0065d9, 0x0065e0, 0x0065e1, 0x0065f1, 0x006772, 0x00660a, 0x006603, + 0x0065fb, 0x006773, 0x006635, 0x006636, 0x006634, 0x00661c, 0x00664f, 0x006644, + 0x006649, 0x006641, 0x00665e, 0x00665d, 0x006664, 0x006667, 0x006668, 0x00665f, + 0x006662, 0x006670, 0x006683, 0x006688, 0x00668e, 0x006689, 0x006684, 0x006698, + 0x00669d, 0x0066c1, 0x0066b9, 0x0066c9, 0x0066be, 0x0066bc, + // plane 1 row 59 + 0x0066c4, 0x0066b8, 0x0066d6, 0x0066da, 0x0066e0, 0x00663f, 0x0066e6, 0x0066e9, + 0x0066f0, 0x0066f5, 0x0066f7, 0x00670f, 0x006716, 0x00671e, 0x006726, 0x006727, + 0x009738, 0x00672e, 0x00673f, 0x006736, 0x006741, 0x006738, 0x006737, 0x006746, + 0x00675e, 0x006760, 0x006759, 0x006763, 0x006764, 0x006789, 0x006770, 0x0067a9, + 0x00677c, 0x00676a, 0x00678c, 0x00678b, 0x0067a6, 0x0067a1, 0x006785, 0x0067b7, + 0x0067ef, 0x0067b4, 0x0067ec, 0x0067b3, 0x0067e9, 0x0067b8, 0x0067e4, 0x0067de, + 0x0067dd, 0x0067e2, 0x0067ee, 0x0067b9, 0x0067ce, 0x0067c6, 0x0067e7, 0x006a9c, + 0x00681e, 0x006846, 0x006829, 0x006840, 0x00684d, 0x006832, 0x00684e, 0x0068b3, + 0x00682b, 0x006859, 0x006863, 0x006877, 0x00687f, 0x00689f, 0x00688f, 0x0068ad, + 0x006894, 0x00689d, 0x00689b, 0x006883, 0x006aae, 0x0068b9, 0x006874, 0x0068b5, + 0x0068a0, 0x0068ba, 0x00690f, 0x00688d, 0x00687e, 0x006901, 0x0068ca, 0x006908, + 0x0068d8, 0x006922, 0x006926, 0x0068e1, 0x00690c, 0x0068cd, + // plane 1 row 60 + 0x0068d4, 0x0068e7, 0x0068d5, 0x006936, 0x006912, 0x006904, 0x0068d7, 0x0068e3, + 0x006925, 0x0068f9, 0x0068e0, 0x0068ef, 0x006928, 0x00692a, 0x00691a, 0x006923, + 0x006921, 0x0068c6, 0x006979, 0x006977, 0x00695c, 0x006978, 0x00696b, 0x006954, + 0x00697e, 0x00696e, 0x006939, 0x006974, 0x00693d, 0x006959, 0x006930, 0x006961, + 0x00695e, 0x00695d, 0x006981, 0x00696a, 0x0069b2, 0x0069ae, 0x0069d0, 0x0069bf, + 0x0069c1, 0x0069d3, 0x0069be, 0x0069ce, 0x005be8, 0x0069ca, 0x0069dd, 0x0069bb, + 0x0069c3, 0x0069a7, 0x006a2e, 0x006991, 0x0069a0, 0x00699c, 0x006995, 0x0069b4, + 0x0069de, 0x0069e8, 0x006a02, 0x006a1b, 0x0069ff, 0x006b0a, 0x0069f9, 0x0069f2, + 0x0069e7, 0x006a05, 0x0069b1, 0x006a1e, 0x0069ed, 0x006a14, 0x0069eb, 0x006a0a, + 0x006a12, 0x006ac1, 0x006a23, 0x006a13, 0x006a44, 0x006a0c, 0x006a72, 0x006a36, + 0x006a78, 0x006a47, 0x006a62, 0x006a59, 0x006a66, 0x006a48, 0x006a38, 0x006a22, + 0x006a90, 0x006a8d, 0x006aa0, 0x006a84, 0x006aa2, 0x006aa3, + // plane 1 row 61 + 0x006a97, 0x008617, 0x006abb, 0x006ac3, 0x006ac2, 0x006ab8, 0x006ab3, 0x006aac, + 0x006ade, 0x006ad1, 0x006adf, 0x006aaa, 0x006ada, 0x006aea, 0x006afb, 0x006b05, + 0x008616, 0x006afa, 0x006b12, 0x006b16, 0x009b31, 0x006b1f, 0x006b38, 0x006b37, + 0x0076dc, 0x006b39, 0x0098ee, 0x006b47, 0x006b43, 0x006b49, 0x006b50, 0x006b59, + 0x006b54, 0x006b5b, 0x006b5f, 0x006b61, 0x006b78, 0x006b79, 0x006b7f, 0x006b80, + 0x006b84, 0x006b83, 0x006b8d, 0x006b98, 0x006b95, 0x006b9e, 0x006ba4, 0x006baa, + 0x006bab, 0x006baf, 0x006bb2, 0x006bb1, 0x006bb3, 0x006bb7, 0x006bbc, 0x006bc6, + 0x006bcb, 0x006bd3, 0x006bdf, 0x006bec, 0x006beb, 0x006bf3, 0x006bef, 0x009ebe, + 0x006c08, 0x006c13, 0x006c14, 0x006c1b, 0x006c24, 0x006c23, 0x006c5e, 0x006c55, + 0x006c62, 0x006c6a, 0x006c82, 0x006c8d, 0x006c9a, 0x006c81, 0x006c9b, 0x006c7e, + 0x006c68, 0x006c73, 0x006c92, 0x006c90, 0x006cc4, 0x006cf1, 0x006cd3, 0x006cbd, + 0x006cd7, 0x006cc5, 0x006cdd, 0x006cae, 0x006cb1, 0x006cbe, + // plane 1 row 62 + 0x006cba, 0x006cdb, 0x006cef, 0x006cd9, 0x006cea, 0x006d1f, 0x00884d, 0x006d36, + 0x006d2b, 0x006d3d, 0x006d38, 0x006d19, 0x006d35, 0x006d33, 0x006d12, 0x006d0c, + 0x006d63, 0x006d93, 0x006d64, 0x006d5a, 0x006d79, 0x006d59, 0x006d8e, 0x006d95, + 0x006fe4, 0x006d85, 0x006df9, 0x006e15, 0x006e0a, 0x006db5, 0x006dc7, 0x006de6, + 0x006db8, 0x006dc6, 0x006dec, 0x006dde, 0x006dcc, 0x006de8, 0x006dd2, 0x006dc5, + 0x006dfa, 0x006dd9, 0x006de4, 0x006dd5, 0x006dea, 0x006dee, 0x006e2d, 0x006e6e, + 0x006e2e, 0x006e19, 0x006e72, 0x006e5f, 0x006e3e, 0x006e23, 0x006e6b, 0x006e2b, + 0x006e76, 0x006e4d, 0x006e1f, 0x006e43, 0x006e3a, 0x006e4e, 0x006e24, 0x006eff, + 0x006e1d, 0x006e38, 0x006e82, 0x006eaa, 0x006e98, 0x006ec9, 0x006eb7, 0x006ed3, + 0x006ebd, 0x006eaf, 0x006ec4, 0x006eb2, 0x006ed4, 0x006ed5, 0x006e8f, 0x006ea5, + 0x006ec2, 0x006e9f, 0x006f41, 0x006f11, 0x00704c, 0x006eec, 0x006ef8, 0x006efe, + 0x006f3f, 0x006ef2, 0x006f31, 0x006eef, 0x006f32, 0x006ecc, + // plane 1 row 63 + 0x006f3e, 0x006f13, 0x006ef7, 0x006f86, 0x006f7a, 0x006f78, 0x006f81, 0x006f80, + 0x006f6f, 0x006f5b, 0x006ff3, 0x006f6d, 0x006f82, 0x006f7c, 0x006f58, 0x006f8e, + 0x006f91, 0x006fc2, 0x006f66, 0x006fb3, 0x006fa3, 0x006fa1, 0x006fa4, 0x006fb9, + 0x006fc6, 0x006faa, 0x006fdf, 0x006fd5, 0x006fec, 0x006fd4, 0x006fd8, 0x006ff1, + 0x006fee, 0x006fdb, 0x007009, 0x00700b, 0x006ffa, 0x007011, 0x007001, 0x00700f, + 0x006ffe, 0x00701b, 0x00701a, 0x006f74, 0x00701d, 0x007018, 0x00701f, 0x007030, + 0x00703e, 0x007032, 0x007051, 0x007063, 0x007099, 0x007092, 0x0070af, 0x0070f1, + 0x0070ac, 0x0070b8, 0x0070b3, 0x0070ae, 0x0070df, 0x0070cb, 0x0070dd, 0x0070d9, + 0x007109, 0x0070fd, 0x00711c, 0x007119, 0x007165, 0x007155, 0x007188, 0x007166, + 0x007162, 0x00714c, 0x007156, 0x00716c, 0x00718f, 0x0071fb, 0x007184, 0x007195, + 0x0071a8, 0x0071ac, 0x0071d7, 0x0071b9, 0x0071be, 0x0071d2, 0x0071c9, 0x0071d4, + 0x0071ce, 0x0071e0, 0x0071ec, 0x0071e7, 0x0071f5, 0x0071fc, + // plane 1 row 64 + 0x0071f9, 0x0071ff, 0x00720d, 0x007210, 0x00721b, 0x007228, 0x00722d, 0x00722c, + 0x007230, 0x007232, 0x00723b, 0x00723c, 0x00723f, 0x007240, 0x007246, 0x00724b, + 0x007258, 0x007274, 0x00727e, 0x007282, 0x007281, 0x007287, 0x007292, 0x007296, + 0x0072a2, 0x0072a7, 0x0072b9, 0x0072b2, 0x0072c3, 0x0072c6, 0x0072c4, 0x0072ce, + 0x0072d2, 0x0072e2, 0x0072e0, 0x0072e1, 0x0072f9, 0x0072f7, 0x00500f, 0x007317, + 0x00730a, 0x00731c, 0x007316, 0x00731d, 0x007334, 0x00732f, 0x007329, 0x007325, + 0x00733e, 0x00734e, 0x00734f, 0x009ed8, 0x007357, 0x00736a, 0x007368, 0x007370, + 0x007378, 0x007375, 0x00737b, 0x00737a, 0x0073c8, 0x0073b3, 0x0073ce, 0x0073bb, + 0x0073c0, 0x0073e5, 0x0073ee, 0x0073de, 0x0074a2, 0x007405, 0x00746f, 0x007425, + 0x0073f8, 0x007432, 0x00743a, 0x007455, 0x00743f, 0x00745f, 0x007459, 0x007441, + 0x00745c, 0x007469, 0x007470, 0x007463, 0x00746a, 0x007476, 0x00747e, 0x00748b, + 0x00749e, 0x0074a7, 0x0074ca, 0x0074cf, 0x0074d4, 0x0073f1, + // plane 1 row 65 + 0x0074e0, 0x0074e3, 0x0074e7, 0x0074e9, 0x0074ee, 0x0074f2, 0x0074f0, 0x0074f1, + 0x0074f8, 0x0074f7, 0x007504, 0x007503, 0x007505, 0x00750c, 0x00750e, 0x00750d, + 0x007515, 0x007513, 0x00751e, 0x007526, 0x00752c, 0x00753c, 0x007544, 0x00754d, + 0x00754a, 0x007549, 0x00755b, 0x007546, 0x00755a, 0x007569, 0x007564, 0x007567, + 0x00756b, 0x00756d, 0x007578, 0x007576, 0x007586, 0x007587, 0x007574, 0x00758a, + 0x007589, 0x007582, 0x007594, 0x00759a, 0x00759d, 0x0075a5, 0x0075a3, 0x0075c2, + 0x0075b3, 0x0075c3, 0x0075b5, 0x0075bd, 0x0075b8, 0x0075bc, 0x0075b1, 0x0075cd, + 0x0075ca, 0x0075d2, 0x0075d9, 0x0075e3, 0x0075de, 0x0075fe, 0x0075ff, 0x0075fc, + 0x007601, 0x0075f0, 0x0075fa, 0x0075f2, 0x0075f3, 0x00760b, 0x00760d, 0x007609, + 0x00761f, 0x007627, 0x007620, 0x007621, 0x007622, 0x007624, 0x007634, 0x007630, + 0x00763b, 0x007647, 0x007648, 0x007646, 0x00765c, 0x007658, 0x007661, 0x007662, + 0x007668, 0x007669, 0x00766a, 0x007667, 0x00766c, 0x007670, + // plane 1 row 66 + 0x007672, 0x007676, 0x007678, 0x00767c, 0x007680, 0x007683, 0x007688, 0x00768b, + 0x00768e, 0x007696, 0x007693, 0x007699, 0x00769a, 0x0076b0, 0x0076b4, 0x0076b8, + 0x0076b9, 0x0076ba, 0x0076c2, 0x0076cd, 0x0076d6, 0x0076d2, 0x0076de, 0x0076e1, + 0x0076e5, 0x0076e7, 0x0076ea, 0x00862f, 0x0076fb, 0x007708, 0x007707, 0x007704, + 0x007729, 0x007724, 0x00771e, 0x007725, 0x007726, 0x00771b, 0x007737, 0x007738, + 0x007747, 0x00775a, 0x007768, 0x00776b, 0x00775b, 0x007765, 0x00777f, 0x00777e, + 0x007779, 0x00778e, 0x00778b, 0x007791, 0x0077a0, 0x00779e, 0x0077b0, 0x0077b6, + 0x0077b9, 0x0077bf, 0x0077bc, 0x0077bd, 0x0077bb, 0x0077c7, 0x0077cd, 0x0077d7, + 0x0077da, 0x0077dc, 0x0077e3, 0x0077ee, 0x0077fc, 0x00780c, 0x007812, 0x007926, + 0x007820, 0x00792a, 0x007845, 0x00788e, 0x007874, 0x007886, 0x00787c, 0x00789a, + 0x00788c, 0x0078a3, 0x0078b5, 0x0078aa, 0x0078af, 0x0078d1, 0x0078c6, 0x0078cb, + 0x0078d4, 0x0078be, 0x0078bc, 0x0078c5, 0x0078ca, 0x0078ec, + // plane 1 row 67 + 0x0078e7, 0x0078da, 0x0078fd, 0x0078f4, 0x007907, 0x007912, 0x007911, 0x007919, + 0x00792c, 0x00792b, 0x007940, 0x007960, 0x007957, 0x00795f, 0x00795a, 0x007955, + 0x007953, 0x00797a, 0x00797f, 0x00798a, 0x00799d, 0x0079a7, 0x009f4b, 0x0079aa, + 0x0079ae, 0x0079b3, 0x0079b9, 0x0079ba, 0x0079c9, 0x0079d5, 0x0079e7, 0x0079ec, + 0x0079e1, 0x0079e3, 0x007a08, 0x007a0d, 0x007a18, 0x007a19, 0x007a20, 0x007a1f, + 0x007980, 0x007a31, 0x007a3b, 0x007a3e, 0x007a37, 0x007a43, 0x007a57, 0x007a49, + 0x007a61, 0x007a62, 0x007a69, 0x009f9d, 0x007a70, 0x007a79, 0x007a7d, 0x007a88, + 0x007a97, 0x007a95, 0x007a98, 0x007a96, 0x007aa9, 0x007ac8, 0x007ab0, 0x007ab6, + 0x007ac5, 0x007ac4, 0x007abf, 0x009083, 0x007ac7, 0x007aca, 0x007acd, 0x007acf, + 0x007ad5, 0x007ad3, 0x007ad9, 0x007ada, 0x007add, 0x007ae1, 0x007ae2, 0x007ae6, + 0x007aed, 0x007af0, 0x007b02, 0x007b0f, 0x007b0a, 0x007b06, 0x007b33, 0x007b18, + 0x007b19, 0x007b1e, 0x007b35, 0x007b28, 0x007b36, 0x007b50, + // plane 1 row 68 + 0x007b7a, 0x007b04, 0x007b4d, 0x007b0b, 0x007b4c, 0x007b45, 0x007b75, 0x007b65, + 0x007b74, 0x007b67, 0x007b70, 0x007b71, 0x007b6c, 0x007b6e, 0x007b9d, 0x007b98, + 0x007b9f, 0x007b8d, 0x007b9c, 0x007b9a, 0x007b8b, 0x007b92, 0x007b8f, 0x007b5d, + 0x007b99, 0x007bcb, 0x007bc1, 0x007bcc, 0x007bcf, 0x007bb4, 0x007bc6, 0x007bdd, + 0x007be9, 0x007c11, 0x007c14, 0x007be6, 0x007be5, 0x007c60, 0x007c00, 0x007c07, + 0x007c13, 0x007bf3, 0x007bf7, 0x007c17, 0x007c0d, 0x007bf6, 0x007c23, 0x007c27, + 0x007c2a, 0x007c1f, 0x007c37, 0x007c2b, 0x007c3d, 0x007c4c, 0x007c43, 0x007c54, + 0x007c4f, 0x007c40, 0x007c50, 0x007c58, 0x007c5f, 0x007c64, 0x007c56, 0x007c65, + 0x007c6c, 0x007c75, 0x007c83, 0x007c90, 0x007ca4, 0x007cad, 0x007ca2, 0x007cab, + 0x007ca1, 0x007ca8, 0x007cb3, 0x007cb2, 0x007cb1, 0x007cae, 0x007cb9, 0x007cbd, + 0x007cc0, 0x007cc5, 0x007cc2, 0x007cd8, 0x007cd2, 0x007cdc, 0x007ce2, 0x009b3b, + 0x007cef, 0x007cf2, 0x007cf4, 0x007cf6, 0x007cfa, 0x007d06, + // plane 1 row 69 + 0x007d02, 0x007d1c, 0x007d15, 0x007d0a, 0x007d45, 0x007d4b, 0x007d2e, 0x007d32, + 0x007d3f, 0x007d35, 0x007d46, 0x007d73, 0x007d56, 0x007d4e, 0x007d72, 0x007d68, + 0x007d6e, 0x007d4f, 0x007d63, 0x007d93, 0x007d89, 0x007d5b, 0x007d8f, 0x007d7d, + 0x007d9b, 0x007dba, 0x007dae, 0x007da3, 0x007db5, 0x007dc7, 0x007dbd, 0x007dab, + 0x007e3d, 0x007da2, 0x007daf, 0x007ddc, 0x007db8, 0x007d9f, 0x007db0, 0x007dd8, + 0x007ddd, 0x007de4, 0x007dde, 0x007dfb, 0x007df2, 0x007de1, 0x007e05, 0x007e0a, + 0x007e23, 0x007e21, 0x007e12, 0x007e31, 0x007e1f, 0x007e09, 0x007e0b, 0x007e22, + 0x007e46, 0x007e66, 0x007e3b, 0x007e35, 0x007e39, 0x007e43, 0x007e37, 0x007e32, + 0x007e3a, 0x007e67, 0x007e5d, 0x007e56, 0x007e5e, 0x007e59, 0x007e5a, 0x007e79, + 0x007e6a, 0x007e69, 0x007e7c, 0x007e7b, 0x007e83, 0x007dd5, 0x007e7d, 0x008fae, + 0x007e7f, 0x007e88, 0x007e89, 0x007e8c, 0x007e92, 0x007e90, 0x007e93, 0x007e94, + 0x007e96, 0x007e8e, 0x007e9b, 0x007e9c, 0x007f38, 0x007f3a, + // plane 1 row 70 + 0x007f45, 0x007f4c, 0x007f4d, 0x007f4e, 0x007f50, 0x007f51, 0x007f55, 0x007f54, + 0x007f58, 0x007f5f, 0x007f60, 0x007f68, 0x007f69, 0x007f67, 0x007f78, 0x007f82, + 0x007f86, 0x007f83, 0x007f88, 0x007f87, 0x007f8c, 0x007f94, 0x007f9e, 0x007f9d, + 0x007f9a, 0x007fa3, 0x007faf, 0x007fb2, 0x007fb9, 0x007fae, 0x007fb6, 0x007fb8, + 0x008b71, 0x007fc5, 0x007fc6, 0x007fca, 0x007fd5, 0x007fd4, 0x007fe1, 0x007fe6, + 0x007fe9, 0x007ff3, 0x007ff9, 0x0098dc, 0x008006, 0x008004, 0x00800b, 0x008012, + 0x008018, 0x008019, 0x00801c, 0x008021, 0x008028, 0x00803f, 0x00803b, 0x00804a, + 0x008046, 0x008052, 0x008058, 0x00805a, 0x00805f, 0x008062, 0x008068, 0x008073, + 0x008072, 0x008070, 0x008076, 0x008079, 0x00807d, 0x00807f, 0x008084, 0x008086, + 0x008085, 0x00809b, 0x008093, 0x00809a, 0x0080ad, 0x005190, 0x0080ac, 0x0080db, + 0x0080e5, 0x0080d9, 0x0080dd, 0x0080c4, 0x0080da, 0x0080d6, 0x008109, 0x0080ef, + 0x0080f1, 0x00811b, 0x008129, 0x008123, 0x00812f, 0x00814b, + // plane 1 row 71 + 0x00968b, 0x008146, 0x00813e, 0x008153, 0x008151, 0x0080fc, 0x008171, 0x00816e, + 0x008165, 0x008166, 0x008174, 0x008183, 0x008188, 0x00818a, 0x008180, 0x008182, + 0x0081a0, 0x008195, 0x0081a4, 0x0081a3, 0x00815f, 0x008193, 0x0081a9, 0x0081b0, + 0x0081b5, 0x0081be, 0x0081b8, 0x0081bd, 0x0081c0, 0x0081c2, 0x0081ba, 0x0081c9, + 0x0081cd, 0x0081d1, 0x0081d9, 0x0081d8, 0x0081c8, 0x0081da, 0x0081df, 0x0081e0, + 0x0081e7, 0x0081fa, 0x0081fb, 0x0081fe, 0x008201, 0x008202, 0x008205, 0x008207, + 0x00820a, 0x00820d, 0x008210, 0x008216, 0x008229, 0x00822b, 0x008238, 0x008233, + 0x008240, 0x008259, 0x008258, 0x00825d, 0x00825a, 0x00825f, 0x008264, 0x008262, + 0x008268, 0x00826a, 0x00826b, 0x00822e, 0x008271, 0x008277, 0x008278, 0x00827e, + 0x00828d, 0x008292, 0x0082ab, 0x00829f, 0x0082bb, 0x0082ac, 0x0082e1, 0x0082e3, + 0x0082df, 0x0082d2, 0x0082f4, 0x0082f3, 0x0082fa, 0x008393, 0x008303, 0x0082fb, + 0x0082f9, 0x0082de, 0x008306, 0x0082dc, 0x008309, 0x0082d9, + // plane 1 row 72 + 0x008335, 0x008334, 0x008316, 0x008332, 0x008331, 0x008340, 0x008339, 0x008350, + 0x008345, 0x00832f, 0x00832b, 0x008317, 0x008318, 0x008385, 0x00839a, 0x0083aa, + 0x00839f, 0x0083a2, 0x008396, 0x008323, 0x00838e, 0x008387, 0x00838a, 0x00837c, + 0x0083b5, 0x008373, 0x008375, 0x0083a0, 0x008389, 0x0083a8, 0x0083f4, 0x008413, + 0x0083eb, 0x0083ce, 0x0083fd, 0x008403, 0x0083d8, 0x00840b, 0x0083c1, 0x0083f7, + 0x008407, 0x0083e0, 0x0083f2, 0x00840d, 0x008422, 0x008420, 0x0083bd, 0x008438, + 0x008506, 0x0083fb, 0x00846d, 0x00842a, 0x00843c, 0x00855a, 0x008484, 0x008477, + 0x00846b, 0x0084ad, 0x00846e, 0x008482, 0x008469, 0x008446, 0x00842c, 0x00846f, + 0x008479, 0x008435, 0x0084ca, 0x008462, 0x0084b9, 0x0084bf, 0x00849f, 0x0084d9, + 0x0084cd, 0x0084bb, 0x0084da, 0x0084d0, 0x0084c1, 0x0084c6, 0x0084d6, 0x0084a1, + 0x008521, 0x0084ff, 0x0084f4, 0x008517, 0x008518, 0x00852c, 0x00851f, 0x008515, + 0x008514, 0x0084fc, 0x008540, 0x008563, 0x008558, 0x008548, + // plane 1 row 73 + 0x008541, 0x008602, 0x00854b, 0x008555, 0x008580, 0x0085a4, 0x008588, 0x008591, + 0x00858a, 0x0085a8, 0x00856d, 0x008594, 0x00859b, 0x0085ea, 0x008587, 0x00859c, + 0x008577, 0x00857e, 0x008590, 0x0085c9, 0x0085ba, 0x0085cf, 0x0085b9, 0x0085d0, + 0x0085d5, 0x0085dd, 0x0085e5, 0x0085dc, 0x0085f9, 0x00860a, 0x008613, 0x00860b, + 0x0085fe, 0x0085fa, 0x008606, 0x008622, 0x00861a, 0x008630, 0x00863f, 0x00864d, + 0x004e55, 0x008654, 0x00865f, 0x008667, 0x008671, 0x008693, 0x0086a3, 0x0086a9, + 0x0086aa, 0x00868b, 0x00868c, 0x0086b6, 0x0086af, 0x0086c4, 0x0086c6, 0x0086b0, + 0x0086c9, 0x008823, 0x0086ab, 0x0086d4, 0x0086de, 0x0086e9, 0x0086ec, 0x0086df, + 0x0086db, 0x0086ef, 0x008712, 0x008706, 0x008708, 0x008700, 0x008703, 0x0086fb, + 0x008711, 0x008709, 0x00870d, 0x0086f9, 0x00870a, 0x008734, 0x00873f, 0x008737, + 0x00873b, 0x008725, 0x008729, 0x00871a, 0x008760, 0x00875f, 0x008778, 0x00874c, + 0x00874e, 0x008774, 0x008757, 0x008768, 0x00876e, 0x008759, + // plane 1 row 74 + 0x008753, 0x008763, 0x00876a, 0x008805, 0x0087a2, 0x00879f, 0x008782, 0x0087af, + 0x0087cb, 0x0087bd, 0x0087c0, 0x0087d0, 0x0096d6, 0x0087ab, 0x0087c4, 0x0087b3, + 0x0087c7, 0x0087c6, 0x0087bb, 0x0087ef, 0x0087f2, 0x0087e0, 0x00880f, 0x00880d, + 0x0087fe, 0x0087f6, 0x0087f7, 0x00880e, 0x0087d2, 0x008811, 0x008816, 0x008815, + 0x008822, 0x008821, 0x008831, 0x008836, 0x008839, 0x008827, 0x00883b, 0x008844, + 0x008842, 0x008852, 0x008859, 0x00885e, 0x008862, 0x00886b, 0x008881, 0x00887e, + 0x00889e, 0x008875, 0x00887d, 0x0088b5, 0x008872, 0x008882, 0x008897, 0x008892, + 0x0088ae, 0x008899, 0x0088a2, 0x00888d, 0x0088a4, 0x0088b0, 0x0088bf, 0x0088b1, + 0x0088c3, 0x0088c4, 0x0088d4, 0x0088d8, 0x0088d9, 0x0088dd, 0x0088f9, 0x008902, + 0x0088fc, 0x0088f4, 0x0088e8, 0x0088f2, 0x008904, 0x00890c, 0x00890a, 0x008913, + 0x008943, 0x00891e, 0x008925, 0x00892a, 0x00892b, 0x008941, 0x008944, 0x00893b, + 0x008936, 0x008938, 0x00894c, 0x00891d, 0x008960, 0x00895e, + // plane 1 row 75 + 0x008966, 0x008964, 0x00896d, 0x00896a, 0x00896f, 0x008974, 0x008977, 0x00897e, + 0x008983, 0x008988, 0x00898a, 0x008993, 0x008998, 0x0089a1, 0x0089a9, 0x0089a6, + 0x0089ac, 0x0089af, 0x0089b2, 0x0089ba, 0x0089bd, 0x0089bf, 0x0089c0, 0x0089da, + 0x0089dc, 0x0089dd, 0x0089e7, 0x0089f4, 0x0089f8, 0x008a03, 0x008a16, 0x008a10, + 0x008a0c, 0x008a1b, 0x008a1d, 0x008a25, 0x008a36, 0x008a41, 0x008a5b, 0x008a52, + 0x008a46, 0x008a48, 0x008a7c, 0x008a6d, 0x008a6c, 0x008a62, 0x008a85, 0x008a82, + 0x008a84, 0x008aa8, 0x008aa1, 0x008a91, 0x008aa5, 0x008aa6, 0x008a9a, 0x008aa3, + 0x008ac4, 0x008acd, 0x008ac2, 0x008ada, 0x008aeb, 0x008af3, 0x008ae7, 0x008ae4, + 0x008af1, 0x008b14, 0x008ae0, 0x008ae2, 0x008af7, 0x008ade, 0x008adb, 0x008b0c, + 0x008b07, 0x008b1a, 0x008ae1, 0x008b16, 0x008b10, 0x008b17, 0x008b20, 0x008b33, + 0x0097ab, 0x008b26, 0x008b2b, 0x008b3e, 0x008b28, 0x008b41, 0x008b4c, 0x008b4f, + 0x008b4e, 0x008b49, 0x008b56, 0x008b5b, 0x008b5a, 0x008b6b, + // plane 1 row 76 + 0x008b5f, 0x008b6c, 0x008b6f, 0x008b74, 0x008b7d, 0x008b80, 0x008b8c, 0x008b8e, + 0x008b92, 0x008b93, 0x008b96, 0x008b99, 0x008b9a, 0x008c3a, 0x008c41, 0x008c3f, + 0x008c48, 0x008c4c, 0x008c4e, 0x008c50, 0x008c55, 0x008c62, 0x008c6c, 0x008c78, + 0x008c7a, 0x008c82, 0x008c89, 0x008c85, 0x008c8a, 0x008c8d, 0x008c8e, 0x008c94, + 0x008c7c, 0x008c98, 0x00621d, 0x008cad, 0x008caa, 0x008cbd, 0x008cb2, 0x008cb3, + 0x008cae, 0x008cb6, 0x008cc8, 0x008cc1, 0x008ce4, 0x008ce3, 0x008cda, 0x008cfd, + 0x008cfa, 0x008cfb, 0x008d04, 0x008d05, 0x008d0a, 0x008d07, 0x008d0f, 0x008d0d, + 0x008d10, 0x009f4e, 0x008d13, 0x008ccd, 0x008d14, 0x008d16, 0x008d67, 0x008d6d, + 0x008d71, 0x008d73, 0x008d81, 0x008d99, 0x008dc2, 0x008dbe, 0x008dba, 0x008dcf, + 0x008dda, 0x008dd6, 0x008dcc, 0x008ddb, 0x008dcb, 0x008dea, 0x008deb, 0x008ddf, + 0x008de3, 0x008dfc, 0x008e08, 0x008e09, 0x008dff, 0x008e1d, 0x008e1e, 0x008e10, + 0x008e1f, 0x008e42, 0x008e35, 0x008e30, 0x008e34, 0x008e4a, + // plane 1 row 77 + 0x008e47, 0x008e49, 0x008e4c, 0x008e50, 0x008e48, 0x008e59, 0x008e64, 0x008e60, + 0x008e2a, 0x008e63, 0x008e55, 0x008e76, 0x008e72, 0x008e7c, 0x008e81, 0x008e87, + 0x008e85, 0x008e84, 0x008e8b, 0x008e8a, 0x008e93, 0x008e91, 0x008e94, 0x008e99, + 0x008eaa, 0x008ea1, 0x008eac, 0x008eb0, 0x008ec6, 0x008eb1, 0x008ebe, 0x008ec5, + 0x008ec8, 0x008ecb, 0x008edb, 0x008ee3, 0x008efc, 0x008efb, 0x008eeb, 0x008efe, + 0x008f0a, 0x008f05, 0x008f15, 0x008f12, 0x008f19, 0x008f13, 0x008f1c, 0x008f1f, + 0x008f1b, 0x008f0c, 0x008f26, 0x008f33, 0x008f3b, 0x008f39, 0x008f45, 0x008f42, + 0x008f3e, 0x008f4c, 0x008f49, 0x008f46, 0x008f4e, 0x008f57, 0x008f5c, 0x008f62, + 0x008f63, 0x008f64, 0x008f9c, 0x008f9f, 0x008fa3, 0x008fad, 0x008faf, 0x008fb7, + 0x008fda, 0x008fe5, 0x008fe2, 0x008fea, 0x008fef, 0x009087, 0x008ff4, 0x009005, + 0x008ff9, 0x008ffa, 0x009011, 0x009015, 0x009021, 0x00900d, 0x00901e, 0x009016, + 0x00900b, 0x009027, 0x009036, 0x009035, 0x009039, 0x008ff8, + // plane 1 row 78 + 0x00904f, 0x009050, 0x009051, 0x009052, 0x00900e, 0x009049, 0x00903e, 0x009056, + 0x009058, 0x00905e, 0x009068, 0x00906f, 0x009076, 0x0096a8, 0x009072, 0x009082, + 0x00907d, 0x009081, 0x009080, 0x00908a, 0x009089, 0x00908f, 0x0090a8, 0x0090af, + 0x0090b1, 0x0090b5, 0x0090e2, 0x0090e4, 0x006248, 0x0090db, 0x009102, 0x009112, + 0x009119, 0x009132, 0x009130, 0x00914a, 0x009156, 0x009158, 0x009163, 0x009165, + 0x009169, 0x009173, 0x009172, 0x00918b, 0x009189, 0x009182, 0x0091a2, 0x0091ab, + 0x0091af, 0x0091aa, 0x0091b5, 0x0091b4, 0x0091ba, 0x0091c0, 0x0091c1, 0x0091c9, + 0x0091cb, 0x0091d0, 0x0091d6, 0x0091df, 0x0091e1, 0x0091db, 0x0091fc, 0x0091f5, + 0x0091f6, 0x00921e, 0x0091ff, 0x009214, 0x00922c, 0x009215, 0x009211, 0x00925e, + 0x009257, 0x009245, 0x009249, 0x009264, 0x009248, 0x009295, 0x00923f, 0x00924b, + 0x009250, 0x00929c, 0x009296, 0x009293, 0x00929b, 0x00925a, 0x0092cf, 0x0092b9, + 0x0092b7, 0x0092e9, 0x00930f, 0x0092fa, 0x009344, 0x00932e, + // plane 1 row 79 + 0x009319, 0x009322, 0x00931a, 0x009323, 0x00933a, 0x009335, 0x00933b, 0x00935c, + 0x009360, 0x00937c, 0x00936e, 0x009356, 0x0093b0, 0x0093ac, 0x0093ad, 0x009394, + 0x0093b9, 0x0093d6, 0x0093d7, 0x0093e8, 0x0093e5, 0x0093d8, 0x0093c3, 0x0093dd, + 0x0093d0, 0x0093c8, 0x0093e4, 0x00941a, 0x009414, 0x009413, 0x009403, 0x009407, + 0x009410, 0x009436, 0x00942b, 0x009435, 0x009421, 0x00943a, 0x009441, 0x009452, + 0x009444, 0x00945b, 0x009460, 0x009462, 0x00945e, 0x00946a, 0x009229, 0x009470, + 0x009475, 0x009477, 0x00947d, 0x00945a, 0x00947c, 0x00947e, 0x009481, 0x00947f, + 0x009582, 0x009587, 0x00958a, 0x009594, 0x009596, 0x009598, 0x009599, 0x0095a0, + 0x0095a8, 0x0095a7, 0x0095ad, 0x0095bc, 0x0095bb, 0x0095b9, 0x0095be, 0x0095ca, + 0x006ff6, 0x0095c3, 0x0095cd, 0x0095cc, 0x0095d5, 0x0095d4, 0x0095d6, 0x0095dc, + 0x0095e1, 0x0095e5, 0x0095e2, 0x009621, 0x009628, 0x00962e, 0x00962f, 0x009642, + 0x00964c, 0x00964f, 0x00964b, 0x009677, 0x00965c, 0x00965e, + // plane 1 row 80 + 0x00965d, 0x00965f, 0x009666, 0x009672, 0x00966c, 0x00968d, 0x009698, 0x009695, + 0x009697, 0x0096aa, 0x0096a7, 0x0096b1, 0x0096b2, 0x0096b0, 0x0096b4, 0x0096b6, + 0x0096b8, 0x0096b9, 0x0096ce, 0x0096cb, 0x0096c9, 0x0096cd, 0x00894d, 0x0096dc, + 0x00970d, 0x0096d5, 0x0096f9, 0x009704, 0x009706, 0x009708, 0x009713, 0x00970e, + 0x009711, 0x00970f, 0x009716, 0x009719, 0x009724, 0x00972a, 0x009730, 0x009739, + 0x00973d, 0x00973e, 0x009744, 0x009746, 0x009748, 0x009742, 0x009749, 0x00975c, + 0x009760, 0x009764, 0x009766, 0x009768, 0x0052d2, 0x00976b, 0x009771, 0x009779, + 0x009785, 0x00977c, 0x009781, 0x00977a, 0x009786, 0x00978b, 0x00978f, 0x009790, + 0x00979c, 0x0097a8, 0x0097a6, 0x0097a3, 0x0097b3, 0x0097b4, 0x0097c3, 0x0097c6, + 0x0097c8, 0x0097cb, 0x0097dc, 0x0097ed, 0x009f4f, 0x0097f2, 0x007adf, 0x0097f6, + 0x0097f5, 0x00980f, 0x00980c, 0x009838, 0x009824, 0x009821, 0x009837, 0x00983d, + 0x009846, 0x00984f, 0x00984b, 0x00986b, 0x00986f, 0x009870, + // plane 1 row 81 + 0x009871, 0x009874, 0x009873, 0x0098aa, 0x0098af, 0x0098b1, 0x0098b6, 0x0098c4, + 0x0098c3, 0x0098c6, 0x0098e9, 0x0098eb, 0x009903, 0x009909, 0x009912, 0x009914, + 0x009918, 0x009921, 0x00991d, 0x00991e, 0x009924, 0x009920, 0x00992c, 0x00992e, + 0x00993d, 0x00993e, 0x009942, 0x009949, 0x009945, 0x009950, 0x00994b, 0x009951, + 0x009952, 0x00994c, 0x009955, 0x009997, 0x009998, 0x0099a5, 0x0099ad, 0x0099ae, + 0x0099bc, 0x0099df, 0x0099db, 0x0099dd, 0x0099d8, 0x0099d1, 0x0099ed, 0x0099ee, + 0x0099f1, 0x0099f2, 0x0099fb, 0x0099f8, 0x009a01, 0x009a0f, 0x009a05, 0x0099e2, + 0x009a19, 0x009a2b, 0x009a37, 0x009a45, 0x009a42, 0x009a40, 0x009a43, 0x009a3e, + 0x009a55, 0x009a4d, 0x009a5b, 0x009a57, 0x009a5f, 0x009a62, 0x009a65, 0x009a64, + 0x009a69, 0x009a6b, 0x009a6a, 0x009aad, 0x009ab0, 0x009abc, 0x009ac0, 0x009acf, + 0x009ad1, 0x009ad3, 0x009ad4, 0x009ade, 0x009adf, 0x009ae2, 0x009ae3, 0x009ae6, + 0x009aef, 0x009aeb, 0x009aee, 0x009af4, 0x009af1, 0x009af7, + // plane 1 row 82 + 0x009afb, 0x009b06, 0x009b18, 0x009b1a, 0x009b1f, 0x009b22, 0x009b23, 0x009b25, + 0x009b27, 0x009b28, 0x009b29, 0x009b2a, 0x009b2e, 0x009b2f, 0x009b32, 0x009b44, + 0x009b43, 0x009b4f, 0x009b4d, 0x009b4e, 0x009b51, 0x009b58, 0x009b74, 0x009b93, + 0x009b83, 0x009b91, 0x009b96, 0x009b97, 0x009b9f, 0x009ba0, 0x009ba8, 0x009bb4, + 0x009bc0, 0x009bca, 0x009bb9, 0x009bc6, 0x009bcf, 0x009bd1, 0x009bd2, 0x009be3, + 0x009be2, 0x009be4, 0x009bd4, 0x009be1, 0x009c3a, 0x009bf2, 0x009bf1, 0x009bf0, + 0x009c15, 0x009c14, 0x009c09, 0x009c13, 0x009c0c, 0x009c06, 0x009c08, 0x009c12, + 0x009c0a, 0x009c04, 0x009c2e, 0x009c1b, 0x009c25, 0x009c24, 0x009c21, 0x009c30, + 0x009c47, 0x009c32, 0x009c46, 0x009c3e, 0x009c5a, 0x009c60, 0x009c67, 0x009c76, + 0x009c78, 0x009ce7, 0x009cec, 0x009cf0, 0x009d09, 0x009d08, 0x009ceb, 0x009d03, + 0x009d06, 0x009d2a, 0x009d26, 0x009daf, 0x009d23, 0x009d1f, 0x009d44, 0x009d15, + 0x009d12, 0x009d41, 0x009d3f, 0x009d3e, 0x009d46, 0x009d48, + // plane 1 row 83 + 0x009d5d, 0x009d5e, 0x009d64, 0x009d51, 0x009d50, 0x009d59, 0x009d72, 0x009d89, + 0x009d87, 0x009dab, 0x009d6f, 0x009d7a, 0x009d9a, 0x009da4, 0x009da9, 0x009db2, + 0x009dc4, 0x009dc1, 0x009dbb, 0x009db8, 0x009dba, 0x009dc6, 0x009dcf, 0x009dc2, + 0x009dd9, 0x009dd3, 0x009df8, 0x009de6, 0x009ded, 0x009def, 0x009dfd, 0x009e1a, + 0x009e1b, 0x009e1e, 0x009e75, 0x009e79, 0x009e7d, 0x009e81, 0x009e88, 0x009e8b, + 0x009e8c, 0x009e92, 0x009e95, 0x009e91, 0x009e9d, 0x009ea5, 0x009ea9, 0x009eb8, + 0x009eaa, 0x009ead, 0x009761, 0x009ecc, 0x009ece, 0x009ecf, 0x009ed0, 0x009ed4, + 0x009edc, 0x009ede, 0x009edd, 0x009ee0, 0x009ee5, 0x009ee8, 0x009eef, 0x009ef4, + 0x009ef6, 0x009ef7, 0x009ef9, 0x009efb, 0x009efc, 0x009efd, 0x009f07, 0x009f08, + 0x0076b7, 0x009f15, 0x009f21, 0x009f2c, 0x009f3e, 0x009f4a, 0x009f52, 0x009f54, + 0x009f63, 0x009f5f, 0x009f60, 0x009f61, 0x009f66, 0x009f67, 0x009f6c, 0x009f6a, + 0x009f77, 0x009f72, 0x009f76, 0x009f95, 0x009f9c, 0x009fa0, + // plane 1 row 84 + 0x00582f, 0x0069c7, 0x009059, 0x007464, 0x0051dc, 0x007199, 0x005653, 0x005de2, + 0x005e14, 0x005e18, 0x005e58, 0x005e5e, 0x005ebe, 0x00f928, 0x005ecb, 0x005ef9, + 0x005f00, 0x005f02, 0x005f07, 0x005f1d, 0x005f23, 0x005f34, 0x005f36, 0x005f3d, + 0x005f40, 0x005f45, 0x005f54, 0x005f58, 0x005f64, 0x005f67, 0x005f7d, 0x005f89, + 0x005f9c, 0x005fa7, 0x005faf, 0x005fb5, 0x005fb7, 0x005fc9, 0x005fde, 0x005fe1, + 0x005fe9, 0x00600d, 0x006014, 0x006018, 0x006033, 0x006035, 0x006047, 0x00fa3d, + 0x00609d, 0x00609e, 0x0060cb, 0x0060d4, 0x0060d5, 0x0060dd, 0x0060f8, 0x00611c, + 0x00612b, 0x006130, 0x006137, 0x00fa3e, 0x00618d, 0x00fa3f, 0x0061bc, 0x0061b9, + 0x00fa40, 0x006222, 0x00623e, 0x006243, 0x006256, 0x00625a, 0x00626f, 0x006285, + 0x0062c4, 0x0062d6, 0x0062fc, 0x00630a, 0x006318, 0x006339, 0x006343, 0x006365, + 0x00637c, 0x0063e5, 0x0063ed, 0x0063f5, 0x006410, 0x006414, 0x006422, 0x006479, + 0x006451, 0x006460, 0x00646d, 0x0064ce, 0x0064be, 0x0064bf, + // plane 1 row 85 + 0x0064c4, 0x0064ca, 0x0064d0, 0x0064f7, 0x0064fb, 0x006522, 0x006529, 0x00fa41, + 0x006567, 0x00659d, 0x00fa42, 0x006600, 0x006609, 0x006615, 0x00661e, 0x00663a, + 0x006622, 0x006624, 0x00662b, 0x006630, 0x006631, 0x006633, 0x0066fb, 0x006648, + 0x00664c, 0x0231c4, 0x006659, 0x00665a, 0x006661, 0x006665, 0x006673, 0x006677, + 0x006678, 0x00668d, 0x00fa43, 0x0066a0, 0x0066b2, 0x0066bb, 0x0066c6, 0x0066c8, + 0x003b22, 0x0066db, 0x0066e8, 0x0066fa, 0x006713, 0x00f929, 0x006733, 0x006766, + 0x006747, 0x006748, 0x00677b, 0x006781, 0x006793, 0x006798, 0x00679b, 0x0067bb, + 0x0067f9, 0x0067c0, 0x0067d7, 0x0067fc, 0x006801, 0x006852, 0x00681d, 0x00682c, + 0x006831, 0x00685b, 0x006872, 0x006875, 0x00fa44, 0x0068a3, 0x0068a5, 0x0068b2, + 0x0068c8, 0x0068d0, 0x0068e8, 0x0068ed, 0x0068f0, 0x0068f1, 0x0068fc, 0x00690a, + 0x006949, 0x0235c4, 0x006935, 0x006942, 0x006957, 0x006963, 0x006964, 0x006968, + 0x006980, 0x00fa14, 0x0069a5, 0x0069ad, 0x0069cf, 0x003bb6, + // plane 1 row 86 + 0x003bc3, 0x0069e2, 0x0069e9, 0x0069ea, 0x0069f5, 0x0069f6, 0x006a0f, 0x006a15, + 0x02373f, 0x006a3b, 0x006a3e, 0x006a45, 0x006a50, 0x006a56, 0x006a5b, 0x006a6b, + 0x006a73, 0x023763, 0x006a89, 0x006a94, 0x006a9d, 0x006a9e, 0x006aa5, 0x006ae4, + 0x006ae7, 0x003c0f, 0x00f91d, 0x006b1b, 0x006b1e, 0x006b2c, 0x006b35, 0x006b46, + 0x006b56, 0x006b60, 0x006b65, 0x006b67, 0x006b77, 0x006b82, 0x006ba9, 0x006bad, + 0x00f970, 0x006bcf, 0x006bd6, 0x006bd7, 0x006bff, 0x006c05, 0x006c10, 0x006c33, + 0x006c59, 0x006c5c, 0x006caa, 0x006c74, 0x006c76, 0x006c85, 0x006c86, 0x006c98, + 0x006c9c, 0x006cfb, 0x006cc6, 0x006cd4, 0x006ce0, 0x006ceb, 0x006cee, 0x023cfe, + 0x006d04, 0x006d0e, 0x006d2e, 0x006d31, 0x006d39, 0x006d3f, 0x006d58, 0x006d65, + 0x00fa45, 0x006d82, 0x006d87, 0x006d89, 0x006d94, 0x006daa, 0x006dac, 0x006dbf, + 0x006dc4, 0x006dd6, 0x006dda, 0x006ddb, 0x006ddd, 0x006dfc, 0x00fa46, 0x006e34, + 0x006e44, 0x006e5c, 0x006e5e, 0x006eab, 0x006eb1, 0x006ec1, + // plane 1 row 87 + 0x006ec7, 0x006ece, 0x006f10, 0x006f1a, 0x00fa47, 0x006f2a, 0x006f2f, 0x006f33, + 0x006f51, 0x006f59, 0x006f5e, 0x006f61, 0x006f62, 0x006f7e, 0x006f88, 0x006f8c, + 0x006f8d, 0x006f94, 0x006fa0, 0x006fa7, 0x006fb6, 0x006fbc, 0x006fc7, 0x006fca, + 0x006ff9, 0x006ff0, 0x006ff5, 0x007005, 0x007006, 0x007028, 0x00704a, 0x00705d, + 0x00705e, 0x00704e, 0x007064, 0x007075, 0x007085, 0x0070a4, 0x0070ab, 0x0070b7, + 0x0070d4, 0x0070d8, 0x0070e4, 0x00710f, 0x00712b, 0x00711e, 0x007120, 0x00712e, + 0x007130, 0x007146, 0x007147, 0x007151, 0x00fa48, 0x007152, 0x00715c, 0x007160, + 0x007168, 0x00fa15, 0x007185, 0x007187, 0x007192, 0x0071c1, 0x0071ba, 0x0071c4, + 0x0071fe, 0x007200, 0x007215, 0x007255, 0x007256, 0x003e3f, 0x00728d, 0x00729b, + 0x0072be, 0x0072c0, 0x0072fb, 0x0247f1, 0x007327, 0x007328, 0x00fa16, 0x007350, + 0x007366, 0x00737c, 0x007395, 0x00739f, 0x0073a0, 0x0073a2, 0x0073a6, 0x0073ab, + 0x0073c9, 0x0073cf, 0x0073d6, 0x0073d9, 0x0073e3, 0x0073e9, + // plane 1 row 88 + 0x007407, 0x00740a, 0x00741a, 0x00741b, 0x00fa4a, 0x007426, 0x007428, 0x00742a, + 0x00742b, 0x00742c, 0x00742e, 0x00742f, 0x007430, 0x007444, 0x007446, 0x007447, + 0x00744b, 0x007457, 0x007462, 0x00746b, 0x00746d, 0x007486, 0x007487, 0x007489, + 0x007498, 0x00749c, 0x00749f, 0x0074a3, 0x007490, 0x0074a6, 0x0074a8, 0x0074a9, + 0x0074b5, 0x0074bf, 0x0074c8, 0x0074c9, 0x0074da, 0x0074ff, 0x007501, 0x007517, + 0x00752f, 0x00756f, 0x007579, 0x007592, 0x003f72, 0x0075ce, 0x0075e4, 0x007600, + 0x007602, 0x007608, 0x007615, 0x007616, 0x007619, 0x00761e, 0x00762d, 0x007635, + 0x007643, 0x00764b, 0x007664, 0x007665, 0x00766d, 0x00766f, 0x007671, 0x007681, + 0x00769b, 0x00769d, 0x00769e, 0x0076a6, 0x0076aa, 0x0076b6, 0x0076c5, 0x0076cc, + 0x0076ce, 0x0076d4, 0x0076e6, 0x0076f1, 0x0076fc, 0x00770a, 0x007719, 0x007734, + 0x007736, 0x007746, 0x00774d, 0x00774e, 0x00775c, 0x00775f, 0x007762, 0x00777a, + 0x007780, 0x007794, 0x0077aa, 0x0077e0, 0x00782d, 0x02548e, + // plane 1 row 89 + 0x007843, 0x00784e, 0x00784f, 0x007851, 0x007868, 0x00786e, 0x00fa4b, 0x0078b0, + 0x02550e, 0x0078ad, 0x0078e4, 0x0078f2, 0x007900, 0x0078f7, 0x00791c, 0x00792e, + 0x007931, 0x007934, 0x00fa4c, 0x00fa4d, 0x007945, 0x007946, 0x00fa4e, 0x00fa4f, + 0x00fa50, 0x00795c, 0x00fa51, 0x00fa19, 0x00fa1a, 0x007979, 0x00fa52, 0x00fa53, + 0x00fa1b, 0x007998, 0x0079b1, 0x0079b8, 0x0079c8, 0x0079ca, 0x025771, 0x0079d4, + 0x0079de, 0x0079eb, 0x0079ed, 0x007a03, 0x00fa54, 0x007a39, 0x007a5d, 0x007a6d, + 0x00fa55, 0x007a85, 0x007aa0, 0x0259c4, 0x007ab3, 0x007abb, 0x007ace, 0x007aeb, + 0x007afd, 0x007b12, 0x007b2d, 0x007b3b, 0x007b47, 0x007b4e, 0x007b60, 0x007b6d, + 0x007b6f, 0x007b72, 0x007b9e, 0x00fa56, 0x007bd7, 0x007bd9, 0x007c01, 0x007c31, + 0x007c1e, 0x007c20, 0x007c33, 0x007c36, 0x004264, 0x025da1, 0x007c59, 0x007c6d, + 0x007c79, 0x007c8f, 0x007c94, 0x007ca0, 0x007cbc, 0x007cd5, 0x007cd9, 0x007cdd, + 0x007d07, 0x007d08, 0x007d13, 0x007d1d, 0x007d23, 0x007d31, + // plane 1 row 90 + 0x007d41, 0x007d48, 0x007d53, 0x007d5c, 0x007d7a, 0x007d83, 0x007d8b, 0x007da0, + 0x007da6, 0x007dc2, 0x007dcc, 0x007dd6, 0x007de3, 0x00fa57, 0x007e28, 0x007e08, + 0x007e11, 0x007e15, 0x00fa59, 0x007e47, 0x007e52, 0x007e61, 0x007e8a, 0x007e8d, + 0x007f47, 0x00fa5a, 0x007f91, 0x007f97, 0x007fbf, 0x007fce, 0x007fdb, 0x007fdf, + 0x007fec, 0x007fee, 0x007ffa, 0x00fa5b, 0x008014, 0x008026, 0x008035, 0x008037, + 0x00803c, 0x0080ca, 0x0080d7, 0x0080e0, 0x0080f3, 0x008118, 0x00814a, 0x008160, + 0x008167, 0x008168, 0x00816d, 0x0081bb, 0x0081ca, 0x0081cf, 0x0081d7, 0x00fa5c, + 0x004453, 0x00445b, 0x008260, 0x008274, 0x026aff, 0x00828e, 0x0082a1, 0x0082a3, + 0x0082a4, 0x0082a9, 0x0082ae, 0x0082b7, 0x0082be, 0x0082bf, 0x0082c6, 0x0082d5, + 0x0082fd, 0x0082fe, 0x008300, 0x008301, 0x008362, 0x008322, 0x00832d, 0x00833a, + 0x008343, 0x008347, 0x008351, 0x008355, 0x00837d, 0x008386, 0x008392, 0x008398, + 0x0083a7, 0x0083a9, 0x0083bf, 0x0083c0, 0x0083c7, 0x0083cf, + // plane 1 row 91 + 0x0083d1, 0x0083e1, 0x0083ea, 0x008401, 0x008406, 0x00840a, 0x00fa5f, 0x008448, + 0x00845f, 0x008470, 0x008473, 0x008485, 0x00849e, 0x0084af, 0x0084b4, 0x0084ba, + 0x0084c0, 0x0084c2, 0x026e40, 0x008532, 0x00851e, 0x008523, 0x00852f, 0x008559, + 0x008564, 0x00fa1f, 0x0085ad, 0x00857a, 0x00858c, 0x00858f, 0x0085a2, 0x0085b0, + 0x0085cb, 0x0085ce, 0x0085ed, 0x008612, 0x0085ff, 0x008604, 0x008605, 0x008610, + 0x0270f4, 0x008618, 0x008629, 0x008638, 0x008657, 0x00865b, 0x00f936, 0x008662, + 0x00459d, 0x00866c, 0x008675, 0x008698, 0x0086b8, 0x0086fa, 0x0086fc, 0x0086fd, + 0x00870b, 0x008771, 0x008787, 0x008788, 0x0087ac, 0x0087ad, 0x0087b5, 0x0045ea, + 0x0087d6, 0x0087ec, 0x008806, 0x00880a, 0x008810, 0x008814, 0x00881f, 0x008898, + 0x0088aa, 0x0088ca, 0x0088ce, 0x027684, 0x0088f5, 0x00891c, 0x00fa60, 0x008918, + 0x008919, 0x00891a, 0x008927, 0x008930, 0x008932, 0x008939, 0x008940, 0x008994, + 0x00fa61, 0x0089d4, 0x0089e5, 0x0089f6, 0x008a12, 0x008a15, + // plane 1 row 92 + 0x008a22, 0x008a37, 0x008a47, 0x008a4e, 0x008a5d, 0x008a61, 0x008a75, 0x008a79, + 0x008aa7, 0x008ad0, 0x008adf, 0x008af4, 0x008af6, 0x00fa22, 0x00fa62, 0x00fa63, + 0x008b46, 0x008b54, 0x008b59, 0x008b69, 0x008b9d, 0x008c49, 0x008c68, 0x00fa64, + 0x008ce1, 0x008cf4, 0x008cf8, 0x008cfe, 0x00fa65, 0x008d12, 0x008d1b, 0x008daf, + 0x008dce, 0x008dd1, 0x008dd7, 0x008e20, 0x008e23, 0x008e3d, 0x008e70, 0x008e7b, + 0x028277, 0x008ec0, 0x004844, 0x008efa, 0x008f1e, 0x008f2d, 0x008f36, 0x008f54, + 0x0283cd, 0x008fa6, 0x008fb5, 0x008fe4, 0x008fe8, 0x008fee, 0x009008, 0x00902d, + 0x00fa67, 0x009088, 0x009095, 0x009097, 0x009099, 0x00909b, 0x0090a2, 0x0090b3, + 0x0090be, 0x0090c4, 0x0090c5, 0x0090c7, 0x0090d7, 0x0090dd, 0x0090de, 0x0090ef, + 0x0090f4, 0x00fa26, 0x009114, 0x009115, 0x009116, 0x009122, 0x009123, 0x009127, + 0x00912f, 0x009131, 0x009134, 0x00913d, 0x009148, 0x00915b, 0x009183, 0x00919e, + 0x0091ac, 0x0091b1, 0x0091bc, 0x0091d7, 0x0091fb, 0x0091e4, + // plane 1 row 93 + 0x0091e5, 0x0091ed, 0x0091f1, 0x009207, 0x009210, 0x009238, 0x009239, 0x00923a, + 0x00923c, 0x009240, 0x009243, 0x00924f, 0x009278, 0x009288, 0x0092c2, 0x0092cb, + 0x0092cc, 0x0092d3, 0x0092e0, 0x0092ff, 0x009304, 0x00931f, 0x009321, 0x009325, + 0x009348, 0x009349, 0x00934a, 0x009364, 0x009365, 0x00936a, 0x009370, 0x00939b, + 0x0093a3, 0x0093ba, 0x0093c6, 0x0093de, 0x0093df, 0x009404, 0x0093fd, 0x009433, + 0x00944a, 0x009463, 0x00946b, 0x009471, 0x009472, 0x00958e, 0x00959f, 0x0095a6, + 0x0095a9, 0x0095ac, 0x0095b6, 0x0095bd, 0x0095cb, 0x0095d0, 0x0095d3, 0x0049b0, + 0x0095da, 0x0095de, 0x009658, 0x009684, 0x00f9dc, 0x00969d, 0x0096a4, 0x0096a5, + 0x0096d2, 0x0096de, 0x00fa68, 0x0096e9, 0x0096ef, 0x009733, 0x00973b, 0x00974d, + 0x00974e, 0x00974f, 0x00975a, 0x00976e, 0x009773, 0x009795, 0x0097ae, 0x0097ba, + 0x0097c1, 0x0097c9, 0x0097de, 0x0097db, 0x0097f4, 0x00fa69, 0x00980a, 0x00981e, + 0x00982b, 0x009830, 0x00fa6a, 0x009852, 0x009853, 0x009856, + // plane 1 row 94 + 0x009857, 0x009859, 0x00985a, 0x00f9d0, 0x009865, 0x00986c, 0x0098ba, 0x0098c8, + 0x0098e7, 0x009958, 0x00999e, 0x009a02, 0x009a03, 0x009a24, 0x009a2d, 0x009a2e, + 0x009a38, 0x009a4a, 0x009a4e, 0x009a52, 0x009ab6, 0x009ac1, 0x009ac3, 0x009ace, + 0x009ad6, 0x009af9, 0x009b02, 0x009b08, 0x009b20, 0x004c17, 0x009b2d, 0x009b5e, + 0x009b79, 0x009b66, 0x009b72, 0x009b75, 0x009b84, 0x009b8a, 0x009b8f, 0x009b9e, + 0x009ba7, 0x009bc1, 0x009bce, 0x009be5, 0x009bf8, 0x009bfd, 0x009c00, 0x009c23, + 0x009c41, 0x009c4f, 0x009c50, 0x009c53, 0x009c63, 0x009c65, 0x009c77, 0x009d1d, + 0x009d1e, 0x009d43, 0x009d47, 0x009d52, 0x009d63, 0x009d70, 0x009d7c, 0x009d8a, + 0x009d96, 0x009dc0, 0x009dac, 0x009dbc, 0x009dd7, 0x02a190, 0x009de7, 0x009e07, + 0x009e15, 0x009e7c, 0x009e9e, 0x009ea4, 0x009eac, 0x009eaf, 0x009eb4, 0x009eb5, + 0x009ec3, 0x009ed1, 0x009f10, 0x009f39, 0x009f57, 0x009f90, 0x009f94, 0x009f97, + 0x009fa2, 0x0059f8, 0x005c5b, 0x005e77, 0x007626, 0x007e6b, + // plane 2 row 1 + 0x020089, 0x004e02, 0x004e0f, 0x004e12, 0x004e29, 0x004e2b, 0x004e2e, 0x004e40, + 0x004e47, 0x004e48, 0x0200a2, 0x004e51, 0x003406, 0x0200a4, 0x004e5a, 0x004e69, + 0x004e9d, 0x00342c, 0x00342e, 0x004eb9, 0x004ebb, 0x0201a2, 0x004ebc, 0x004ec3, + 0x004ec8, 0x004ed0, 0x004eeb, 0x004eda, 0x004ef1, 0x004ef5, 0x004f00, 0x004f16, + 0x004f64, 0x004f37, 0x004f3e, 0x004f54, 0x004f58, 0x020213, 0x004f77, 0x004f78, + 0x004f7a, 0x004f7d, 0x004f82, 0x004f85, 0x004f92, 0x004f9a, 0x004fe6, 0x004fb2, + 0x004fbe, 0x004fc5, 0x004fcb, 0x004fcf, 0x004fd2, 0x00346a, 0x004ff2, 0x005000, + 0x005010, 0x005013, 0x00501c, 0x00501e, 0x005022, 0x003468, 0x005042, 0x005046, + 0x00504e, 0x005053, 0x005057, 0x005063, 0x005066, 0x00506a, 0x005070, 0x0050a3, + 0x005088, 0x005092, 0x005093, 0x005095, 0x005096, 0x00509c, 0x0050aa, 0x02032b, + 0x0050b1, 0x0050ba, 0x0050bb, 0x0050c4, 0x0050c7, 0x0050f3, 0x020381, 0x0050ce, + 0x020371, 0x0050d4, 0x0050d9, 0x0050e1, 0x0050e9, 0x003492, + // plane 2 row 2 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 3 + 0x005108, 0x0203f9, 0x005117, 0x00511b, 0x02044a, 0x005160, 0x020509, 0x005173, + 0x005183, 0x00518b, 0x0034bc, 0x005198, 0x0051a3, 0x0051ad, 0x0034c7, 0x0051bc, + 0x0205d6, 0x020628, 0x0051f3, 0x0051f4, 0x005202, 0x005212, 0x005216, 0x02074f, + 0x005255, 0x00525c, 0x00526c, 0x005277, 0x005284, 0x005282, 0x020807, 0x005298, + 0x02083a, 0x0052a4, 0x0052a6, 0x0052af, 0x0052ba, 0x0052bb, 0x0052ca, 0x00351f, + 0x0052d1, 0x0208b9, 0x0052f7, 0x00530a, 0x00530b, 0x005324, 0x005335, 0x00533e, + 0x005342, 0x02097c, 0x02099d, 0x005367, 0x00536c, 0x00537a, 0x0053a4, 0x0053b4, + 0x020ad3, 0x0053b7, 0x0053c0, 0x020b1d, 0x00355d, 0x00355e, 0x0053d5, 0x0053da, + 0x003563, 0x0053f4, 0x0053f5, 0x005455, 0x005424, 0x005428, 0x00356e, 0x005443, + 0x005462, 0x005466, 0x00546c, 0x00548a, 0x00548d, 0x005495, 0x0054a0, 0x0054a6, + 0x0054ad, 0x0054ae, 0x0054b7, 0x0054ba, 0x0054bf, 0x0054c3, 0x020d45, 0x0054ec, + 0x0054ef, 0x0054f1, 0x0054f3, 0x005500, 0x005501, 0x005509, + // plane 2 row 4 + 0x00553c, 0x005541, 0x0035a6, 0x005547, 0x00554a, 0x0035a8, 0x005560, 0x005561, + 0x005564, 0x020de1, 0x00557d, 0x005582, 0x005588, 0x005591, 0x0035c5, 0x0055d2, + 0x020e95, 0x020e6d, 0x0055bf, 0x0055c9, 0x0055cc, 0x0055d1, 0x0055dd, 0x0035da, + 0x0055e2, 0x020e64, 0x0055e9, 0x005628, 0x020f5f, 0x005607, 0x005610, 0x005630, + 0x005637, 0x0035f4, 0x00563d, 0x00563f, 0x005640, 0x005647, 0x00565e, 0x005660, + 0x00566d, 0x003605, 0x005688, 0x00568c, 0x005695, 0x00569a, 0x00569d, 0x0056a8, + 0x0056ad, 0x0056b2, 0x0056c5, 0x0056cd, 0x0056df, 0x0056e8, 0x0056f6, 0x0056f7, + 0x021201, 0x005715, 0x005723, 0x021255, 0x005729, 0x02127b, 0x005745, 0x005746, + 0x00574c, 0x00574d, 0x021274, 0x005768, 0x00576f, 0x005773, 0x005774, 0x005775, + 0x00577b, 0x0212e4, 0x0212d7, 0x0057ac, 0x00579a, 0x00579d, 0x00579e, 0x0057a8, + 0x0057d7, 0x0212fd, 0x0057cc, 0x021336, 0x021344, 0x0057de, 0x0057e6, 0x0057f0, + 0x00364a, 0x0057f8, 0x0057fb, 0x0057fd, 0x005804, 0x00581e, + // plane 2 row 5 + 0x005820, 0x005827, 0x005832, 0x005839, 0x0213c4, 0x005849, 0x00584c, 0x005867, + 0x00588a, 0x00588b, 0x00588d, 0x00588f, 0x005890, 0x005894, 0x00589d, 0x0058aa, + 0x0058b1, 0x02146d, 0x0058c3, 0x0058cd, 0x0058e2, 0x0058f3, 0x0058f4, 0x005905, + 0x005906, 0x00590b, 0x00590d, 0x005914, 0x005924, 0x0215d7, 0x003691, 0x00593d, + 0x003699, 0x005946, 0x003696, 0x026c29, 0x00595b, 0x00595f, 0x021647, 0x005975, + 0x005976, 0x00597c, 0x00599f, 0x0059ae, 0x0059bc, 0x0059c8, 0x0059cd, 0x0059de, + 0x0059e3, 0x0059e4, 0x0059e7, 0x0059ee, 0x021706, 0x021742, 0x0036cf, 0x005a0c, + 0x005a0d, 0x005a17, 0x005a27, 0x005a2d, 0x005a55, 0x005a65, 0x005a7a, 0x005a8b, + 0x005a9c, 0x005a9f, 0x005aa0, 0x005aa2, 0x005ab1, 0x005ab3, 0x005ab5, 0x005aba, + 0x005abf, 0x005ada, 0x005adc, 0x005ae0, 0x005ae5, 0x005af0, 0x005aee, 0x005af5, + 0x005b00, 0x005b08, 0x005b17, 0x005b34, 0x005b2d, 0x005b4c, 0x005b52, 0x005b68, + 0x005b6f, 0x005b7c, 0x005b7f, 0x005b81, 0x005b84, 0x0219c3, + // plane 2 row 6 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 7 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 8 + 0x005b96, 0x005bac, 0x003761, 0x005bc0, 0x003762, 0x005bce, 0x005bd6, 0x00376c, + 0x00376b, 0x005bf1, 0x005bfd, 0x003775, 0x005c03, 0x005c29, 0x005c30, 0x021c56, + 0x005c5f, 0x005c63, 0x005c67, 0x005c68, 0x005c69, 0x005c70, 0x021d2d, 0x021d45, + 0x005c7c, 0x021d78, 0x021d62, 0x005c88, 0x005c8a, 0x0037c1, 0x021da1, 0x021d9c, + 0x005ca0, 0x005ca2, 0x005ca6, 0x005ca7, 0x021d92, 0x005cad, 0x005cb5, 0x021db7, + 0x005cc9, 0x021de0, 0x021e33, 0x005d06, 0x005d10, 0x005d2b, 0x005d1d, 0x005d20, + 0x005d24, 0x005d26, 0x005d31, 0x005d39, 0x005d42, 0x0037e8, 0x005d61, 0x005d6a, + 0x0037f4, 0x005d70, 0x021f1e, 0x0037fd, 0x005d88, 0x003800, 0x005d92, 0x005d94, + 0x005d97, 0x005d99, 0x005db0, 0x005db2, 0x005db4, 0x021f76, 0x005db9, 0x005dd1, + 0x005dd7, 0x005dd8, 0x005de0, 0x021ffa, 0x005de4, 0x005de9, 0x00382f, 0x005e00, + 0x003836, 0x005e12, 0x005e15, 0x003840, 0x005e1f, 0x005e2e, 0x005e3e, 0x005e49, + 0x00385c, 0x005e56, 0x003861, 0x005e6b, 0x005e6c, 0x005e6d, + // plane 2 row 9 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 10 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 11 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 12 + 0x005e6e, 0x02217b, 0x005ea5, 0x005eaa, 0x005eac, 0x005eb9, 0x005ebf, 0x005ec6, + 0x005ed2, 0x005ed9, 0x02231e, 0x005efd, 0x005f08, 0x005f0e, 0x005f1c, 0x0223ad, + 0x005f1e, 0x005f47, 0x005f63, 0x005f72, 0x005f7e, 0x005f8f, 0x005fa2, 0x005fa4, + 0x005fb8, 0x005fc4, 0x0038fa, 0x005fc7, 0x005fcb, 0x005fd2, 0x005fd3, 0x005fd4, + 0x005fe2, 0x005fee, 0x005fef, 0x005ff3, 0x005ffc, 0x003917, 0x006017, 0x006022, + 0x006024, 0x00391a, 0x00604c, 0x00607f, 0x00608a, 0x006095, 0x0060a8, 0x0226f3, + 0x0060b0, 0x0060b1, 0x0060be, 0x0060c8, 0x0060d9, 0x0060db, 0x0060ee, 0x0060f2, + 0x0060f5, 0x006110, 0x006112, 0x006113, 0x006119, 0x00611e, 0x00613a, 0x00396f, + 0x006141, 0x006146, 0x006160, 0x00617c, 0x02285b, 0x006192, 0x006193, 0x006197, + 0x006198, 0x0061a5, 0x0061a8, 0x0061ad, 0x0228ab, 0x0061d5, 0x0061dd, 0x0061df, + 0x0061f5, 0x02298f, 0x006215, 0x006223, 0x006229, 0x006246, 0x00624c, 0x006251, + 0x006252, 0x006261, 0x006264, 0x00627b, 0x00626d, 0x006273, + // plane 2 row 13 + 0x006299, 0x0062a6, 0x0062d5, 0x022ab8, 0x0062fd, 0x006303, 0x00630d, 0x006310, + 0x022b4f, 0x022b50, 0x006332, 0x006335, 0x00633b, 0x00633c, 0x006341, 0x006344, + 0x00634e, 0x022b46, 0x006359, 0x022c1d, 0x022ba6, 0x00636c, 0x006384, 0x006399, + 0x022c24, 0x006394, 0x0063bd, 0x0063f7, 0x0063d4, 0x0063d5, 0x0063dc, 0x0063e0, + 0x0063eb, 0x0063ec, 0x0063f2, 0x006409, 0x00641e, 0x006425, 0x006429, 0x00642f, + 0x00645a, 0x00645b, 0x00645d, 0x006473, 0x00647d, 0x006487, 0x006491, 0x00649d, + 0x00649f, 0x0064cb, 0x0064cc, 0x0064d5, 0x0064d7, 0x022de1, 0x0064e4, 0x0064e5, + 0x0064ff, 0x006504, 0x003a6e, 0x00650f, 0x006514, 0x006516, 0x003a73, 0x00651e, + 0x006532, 0x006544, 0x006554, 0x00656b, 0x00657a, 0x006581, 0x006584, 0x006585, + 0x00658a, 0x0065b2, 0x0065b5, 0x0065b8, 0x0065bf, 0x0065c2, 0x0065c9, 0x0065d4, + 0x003ad6, 0x0065f2, 0x0065f9, 0x0065fc, 0x006604, 0x006608, 0x006621, 0x00662a, + 0x006645, 0x006651, 0x00664e, 0x003aea, 0x0231c3, 0x006657, + // plane 2 row 14 + 0x00665b, 0x006663, 0x0231f5, 0x0231b6, 0x00666a, 0x00666b, 0x00666c, 0x00666d, + 0x00667b, 0x006680, 0x006690, 0x006692, 0x006699, 0x003b0e, 0x0066ad, 0x0066b1, + 0x0066b5, 0x003b1a, 0x0066bf, 0x003b1c, 0x0066ec, 0x003ad7, 0x006701, 0x006705, + 0x006712, 0x023372, 0x006719, 0x0233d3, 0x0233d2, 0x00674c, 0x00674d, 0x006754, + 0x00675d, 0x0233d0, 0x0233e4, 0x0233d5, 0x006774, 0x006776, 0x0233da, 0x006792, + 0x0233df, 0x008363, 0x006810, 0x0067b0, 0x0067b2, 0x0067c3, 0x0067c8, 0x0067d2, + 0x0067d9, 0x0067db, 0x0067f0, 0x0067f7, 0x02344a, 0x023451, 0x02344b, 0x006818, + 0x00681f, 0x00682d, 0x023465, 0x006833, 0x00683b, 0x00683e, 0x006844, 0x006845, + 0x006849, 0x00684c, 0x006855, 0x006857, 0x003b77, 0x00686b, 0x00686e, 0x00687a, + 0x00687c, 0x006882, 0x006890, 0x006896, 0x003b6d, 0x006898, 0x006899, 0x00689a, + 0x00689c, 0x0068aa, 0x0068ab, 0x0068b4, 0x0068bb, 0x0068fb, 0x0234e4, 0x02355a, + 0x00fa13, 0x0068c3, 0x0068c5, 0x0068cc, 0x0068cf, 0x0068d6, + // plane 2 row 15 + 0x0068d9, 0x0068e4, 0x0068e5, 0x0068ec, 0x0068f7, 0x006903, 0x006907, 0x003b87, + 0x003b88, 0x023594, 0x00693b, 0x003b8d, 0x006946, 0x006969, 0x00696c, 0x006972, + 0x00697a, 0x00697f, 0x006992, 0x003ba4, 0x006996, 0x006998, 0x0069a6, 0x0069b0, + 0x0069b7, 0x0069ba, 0x0069bc, 0x0069c0, 0x0069d1, 0x0069d6, 0x023639, 0x023647, + 0x006a30, 0x023638, 0x02363a, 0x0069e3, 0x0069ee, 0x0069ef, 0x0069f3, 0x003bcd, + 0x0069f4, 0x0069fe, 0x006a11, 0x006a1a, 0x006a1d, 0x02371c, 0x006a32, 0x006a33, + 0x006a34, 0x006a3f, 0x006a46, 0x006a49, 0x006a7a, 0x006a4e, 0x006a52, 0x006a64, + 0x02370c, 0x006a7e, 0x006a83, 0x006a8b, 0x003bf0, 0x006a91, 0x006a9f, 0x006aa1, + 0x023764, 0x006aab, 0x006abd, 0x006ac6, 0x006ad4, 0x006ad0, 0x006adc, 0x006add, + 0x0237ff, 0x0237e7, 0x006aec, 0x006af1, 0x006af2, 0x006af3, 0x006afd, 0x023824, + 0x006b0b, 0x006b0f, 0x006b10, 0x006b11, 0x02383d, 0x006b17, 0x003c26, 0x006b2f, + 0x006b4a, 0x006b58, 0x006b6c, 0x006b75, 0x006b7a, 0x006b81, + // plane 2 row 16 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 17 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 18 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 19 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 20 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 21 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 22 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 23 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 24 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 25 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 26 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 27 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 28 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 29 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 30 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 31 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 32 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 33 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 34 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 35 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 36 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 37 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 38 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 39 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 40 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 41 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 42 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 43 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 44 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 45 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 46 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 47 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 48 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 49 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 50 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 51 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 52 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 53 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 54 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 55 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 56 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 57 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 58 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 59 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 60 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 61 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 62 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 63 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 64 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 65 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 66 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 67 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 68 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 69 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 70 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 71 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 72 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 73 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 74 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 75 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 76 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 77 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 78 + 0x006b9b, 0x006bae, 0x023a98, 0x006bbd, 0x006bbe, 0x006bc7, 0x006bc8, 0x006bc9, + 0x006bda, 0x006be6, 0x006be7, 0x006bee, 0x006bf1, 0x006c02, 0x006c0a, 0x006c0e, + 0x006c35, 0x006c36, 0x006c3a, 0x023c7f, 0x006c3f, 0x006c4d, 0x006c5b, 0x006c6d, + 0x006c84, 0x006c89, 0x003cc3, 0x006c94, 0x006c95, 0x006c97, 0x006cad, 0x006cc2, + 0x006cd0, 0x003cd2, 0x006cd6, 0x006cda, 0x006cdc, 0x006ce9, 0x006cec, 0x006ced, + 0x023d00, 0x006d00, 0x006d0a, 0x006d24, 0x006d26, 0x006d27, 0x006c67, 0x006d2f, + 0x006d3c, 0x006d5b, 0x006d5e, 0x006d60, 0x006d70, 0x006d80, 0x006d81, 0x006d8a, + 0x006d8d, 0x006d91, 0x006d98, 0x023d40, 0x006e17, 0x023dfa, 0x023df9, 0x023dd3, + 0x006dab, 0x006dae, 0x006db4, 0x006dc2, 0x006d34, 0x006dc8, 0x006dce, 0x006dcf, + 0x006dd0, 0x006ddf, 0x006de9, 0x006df6, 0x006e36, 0x006e1e, 0x006e22, 0x006e27, + 0x003d11, 0x006e32, 0x006e3c, 0x006e48, 0x006e49, 0x006e4b, 0x006e4c, 0x006e4f, + 0x006e51, 0x006e53, 0x006e54, 0x006e57, 0x006e63, 0x003d1e, + // plane 2 row 79 + 0x006e93, 0x006ea7, 0x006eb4, 0x006ebf, 0x006ec3, 0x006eca, 0x006ed9, 0x006f35, + 0x006eeb, 0x006ef9, 0x006efb, 0x006f0a, 0x006f0c, 0x006f18, 0x006f25, 0x006f36, + 0x006f3c, 0x023f7e, 0x006f52, 0x006f57, 0x006f5a, 0x006f60, 0x006f68, 0x006f98, + 0x006f7d, 0x006f90, 0x006f96, 0x006fbe, 0x006f9f, 0x006fa5, 0x006faf, 0x003d64, + 0x006fb5, 0x006fc8, 0x006fc9, 0x006fda, 0x006fde, 0x006fe9, 0x024096, 0x006ffc, + 0x007000, 0x007007, 0x00700a, 0x007023, 0x024103, 0x007039, 0x00703a, 0x00703c, + 0x007043, 0x007047, 0x00704b, 0x003d9a, 0x007054, 0x007065, 0x007069, 0x00706c, + 0x00706e, 0x007076, 0x00707e, 0x007081, 0x007086, 0x007095, 0x007097, 0x0070bb, + 0x0241c6, 0x00709f, 0x0070b1, 0x0241fe, 0x0070ec, 0x0070ca, 0x0070d1, 0x0070d3, + 0x0070dc, 0x007103, 0x007104, 0x007106, 0x007107, 0x007108, 0x00710c, 0x003dc0, + 0x00712f, 0x007131, 0x007150, 0x00714a, 0x007153, 0x00715e, 0x003dd4, 0x007196, + 0x007180, 0x00719b, 0x0071a0, 0x0071a2, 0x0071ae, 0x0071af, + // plane 2 row 80 + 0x0071b3, 0x0243bc, 0x0071cb, 0x0071d3, 0x0071d9, 0x0071dc, 0x007207, 0x003e05, + 0x00fa49, 0x00722b, 0x007234, 0x007238, 0x007239, 0x004e2c, 0x007242, 0x007253, + 0x007257, 0x007263, 0x024629, 0x00726e, 0x00726f, 0x007278, 0x00727f, 0x00728e, + 0x0246a5, 0x0072ad, 0x0072ae, 0x0072b0, 0x0072b1, 0x0072c1, 0x003e60, 0x0072cc, + 0x003e66, 0x003e68, 0x0072f3, 0x0072fa, 0x007307, 0x007312, 0x007318, 0x007319, + 0x003e83, 0x007339, 0x00732c, 0x007331, 0x007333, 0x00733d, 0x007352, 0x003e94, + 0x00736b, 0x00736c, 0x024896, 0x00736e, 0x00736f, 0x007371, 0x007377, 0x007381, + 0x007385, 0x00738a, 0x007394, 0x007398, 0x00739c, 0x00739e, 0x0073a5, 0x0073a8, + 0x0073b5, 0x0073b7, 0x0073b9, 0x0073bc, 0x0073bf, 0x0073c5, 0x0073cb, 0x0073e1, + 0x0073e7, 0x0073f9, 0x007413, 0x0073fa, 0x007401, 0x007424, 0x007431, 0x007439, + 0x007453, 0x007440, 0x007443, 0x00744d, 0x007452, 0x00745d, 0x007471, 0x007481, + 0x007485, 0x007488, 0x024a4d, 0x007492, 0x007497, 0x007499, + // plane 2 row 81 + 0x0074a0, 0x0074a1, 0x0074a5, 0x0074aa, 0x0074ab, 0x0074b9, 0x0074bb, 0x0074ba, + 0x0074d6, 0x0074d8, 0x0074de, 0x0074ef, 0x0074eb, 0x024b56, 0x0074fa, 0x024b6f, + 0x007520, 0x007524, 0x00752a, 0x003f57, 0x024c16, 0x00753d, 0x00753e, 0x007540, + 0x007548, 0x00754e, 0x007550, 0x007552, 0x00756c, 0x007572, 0x007571, 0x00757a, + 0x00757d, 0x00757e, 0x007581, 0x024d14, 0x00758c, 0x003f75, 0x0075a2, 0x003f77, + 0x0075b0, 0x0075b7, 0x0075bf, 0x0075c0, 0x0075c6, 0x0075cf, 0x0075d3, 0x0075dd, + 0x0075df, 0x0075e0, 0x0075e7, 0x0075ec, 0x0075ee, 0x0075f1, 0x0075f9, 0x007603, + 0x007618, 0x007607, 0x00760f, 0x003fae, 0x024e0e, 0x007613, 0x00761b, 0x00761c, + 0x024e37, 0x007625, 0x007628, 0x00763c, 0x007633, 0x024e6a, 0x003fc9, 0x007641, + 0x024e8b, 0x007649, 0x007655, 0x003fd7, 0x00766e, 0x007695, 0x00769c, 0x0076a1, + 0x0076a0, 0x0076a7, 0x0076a8, 0x0076af, 0x02504a, 0x0076c9, 0x025055, 0x0076e8, + 0x0076ec, 0x025122, 0x007717, 0x00771a, 0x00772d, 0x007735, + // plane 2 row 82 + 0x0251a9, 0x004039, 0x0251e5, 0x0251cd, 0x007758, 0x007760, 0x00776a, 0x02521e, + 0x007772, 0x00777c, 0x00777d, 0x02524c, 0x004058, 0x00779a, 0x00779f, 0x0077a2, + 0x0077a4, 0x0077a9, 0x0077de, 0x0077df, 0x0077e4, 0x0077e6, 0x0077ea, 0x0077ec, + 0x004093, 0x0077f0, 0x0077f4, 0x0077fb, 0x02542e, 0x007805, 0x007806, 0x007809, + 0x00780d, 0x007819, 0x007821, 0x00782c, 0x007847, 0x007864, 0x00786a, 0x0254d9, + 0x00788a, 0x007894, 0x0078a4, 0x00789d, 0x00789e, 0x00789f, 0x0078bb, 0x0078c8, + 0x0078cc, 0x0078ce, 0x0078d5, 0x0078e0, 0x0078e1, 0x0078e6, 0x0078f9, 0x0078fa, + 0x0078fb, 0x0078fe, 0x0255a7, 0x007910, 0x00791b, 0x007930, 0x007925, 0x00793b, + 0x00794a, 0x007958, 0x00795b, 0x004105, 0x007967, 0x007972, 0x007994, 0x007995, + 0x007996, 0x00799b, 0x0079a1, 0x0079a9, 0x0079b4, 0x0079bb, 0x0079c2, 0x0079c7, + 0x0079cc, 0x0079cd, 0x0079d6, 0x004148, 0x0257a9, 0x0257b4, 0x00414f, 0x007a0a, + 0x007a11, 0x007a15, 0x007a1b, 0x007a1e, 0x004163, 0x007a2d, + // plane 2 row 83 + 0x007a38, 0x007a47, 0x007a4c, 0x007a56, 0x007a59, 0x007a5c, 0x007a5f, 0x007a60, + 0x007a67, 0x007a6a, 0x007a75, 0x007a78, 0x007a82, 0x007a8a, 0x007a90, 0x007aa3, + 0x007aac, 0x0259d4, 0x0041b4, 0x007ab9, 0x007abc, 0x007abe, 0x0041bf, 0x007acc, + 0x007ad1, 0x007ae7, 0x007ae8, 0x007af4, 0x025ae4, 0x025ae3, 0x007b07, 0x025af1, + 0x007b3d, 0x007b27, 0x007b2a, 0x007b2e, 0x007b2f, 0x007b31, 0x0041e6, 0x0041f3, + 0x007b7f, 0x007b41, 0x0041ee, 0x007b55, 0x007b79, 0x007b64, 0x007b66, 0x007b69, + 0x007b73, 0x025bb2, 0x004207, 0x007b90, 0x007b91, 0x007b9b, 0x00420e, 0x007baf, + 0x007bb5, 0x007bbc, 0x007bc5, 0x007bca, 0x025c4b, 0x025c64, 0x007bd4, 0x007bd6, + 0x007bda, 0x007bea, 0x007bf0, 0x007c03, 0x007c0b, 0x007c0e, 0x007c0f, 0x007c26, + 0x007c45, 0x007c4a, 0x007c51, 0x007c57, 0x007c5e, 0x007c61, 0x007c69, 0x007c6e, + 0x007c6f, 0x007c70, 0x025e2e, 0x025e56, 0x025e65, 0x007ca6, 0x025e62, 0x007cb6, + 0x007cb7, 0x007cbf, 0x025ed8, 0x007cc4, 0x025ec2, 0x007cc8, + // plane 2 row 84 + 0x007ccd, 0x025ee8, 0x007cd7, 0x025f23, 0x007ce6, 0x007ceb, 0x025f5c, 0x007cf5, + 0x007d03, 0x007d09, 0x0042c6, 0x007d12, 0x007d1e, 0x025fe0, 0x025fd4, 0x007d3d, + 0x007d3e, 0x007d40, 0x007d47, 0x02600c, 0x025ffb, 0x0042d6, 0x007d59, 0x007d5a, + 0x007d6a, 0x007d70, 0x0042dd, 0x007d7f, 0x026017, 0x007d86, 0x007d88, 0x007d8c, + 0x007d97, 0x026060, 0x007d9d, 0x007da7, 0x007daa, 0x007db6, 0x007db7, 0x007dc0, + 0x007dd7, 0x007dd9, 0x007de6, 0x007df1, 0x007df9, 0x004302, 0x0260ed, 0x00fa58, + 0x007e10, 0x007e17, 0x007e1d, 0x007e20, 0x007e27, 0x007e2c, 0x007e45, 0x007e73, + 0x007e75, 0x007e7e, 0x007e86, 0x007e87, 0x00432b, 0x007e91, 0x007e98, 0x007e9a, + 0x004343, 0x007f3c, 0x007f3b, 0x007f3e, 0x007f43, 0x007f44, 0x007f4f, 0x0034c1, + 0x026270, 0x007f52, 0x026286, 0x007f61, 0x007f63, 0x007f64, 0x007f6d, 0x007f7d, + 0x007f7e, 0x02634c, 0x007f90, 0x00517b, 0x023d0e, 0x007f96, 0x007f9c, 0x007fad, + 0x026402, 0x007fc3, 0x007fcf, 0x007fe3, 0x007fe5, 0x007fef, + // plane 2 row 85 + 0x007ff2, 0x008002, 0x00800a, 0x008008, 0x00800e, 0x008011, 0x008016, 0x008024, + 0x00802c, 0x008030, 0x008043, 0x008066, 0x008071, 0x008075, 0x00807b, 0x008099, + 0x00809c, 0x0080a4, 0x0080a7, 0x0080b8, 0x02667e, 0x0080c5, 0x0080d5, 0x0080d8, + 0x0080e6, 0x0266b0, 0x00810d, 0x0080f5, 0x0080fb, 0x0043ee, 0x008135, 0x008116, + 0x00811e, 0x0043f0, 0x008124, 0x008127, 0x00812c, 0x02671d, 0x00813d, 0x004408, + 0x008169, 0x004417, 0x008181, 0x00441c, 0x008184, 0x008185, 0x004422, 0x008198, + 0x0081b2, 0x0081c1, 0x0081c3, 0x0081d6, 0x0081db, 0x0268dd, 0x0081e4, 0x0268ea, + 0x0081ec, 0x026951, 0x0081fd, 0x0081ff, 0x02696f, 0x008204, 0x0269dd, 0x008219, + 0x008221, 0x008222, 0x026a1e, 0x008232, 0x008234, 0x00823c, 0x008246, 0x008249, + 0x008245, 0x026a58, 0x00824b, 0x004476, 0x00824f, 0x00447a, 0x008257, 0x026a8c, + 0x00825c, 0x008263, 0x026ab7, 0x00fa5d, 0x00fa5e, 0x008279, 0x004491, 0x00827d, + 0x00827f, 0x008283, 0x00828a, 0x008293, 0x0082a7, 0x0082a8, + // plane 2 row 86 + 0x0082b2, 0x0082b4, 0x0082ba, 0x0082bc, 0x0082e2, 0x0082e8, 0x0082f7, 0x008307, + 0x008308, 0x00830c, 0x008354, 0x00831b, 0x00831d, 0x008330, 0x00833c, 0x008344, + 0x008357, 0x0044be, 0x00837f, 0x0044d4, 0x0044b3, 0x00838d, 0x008394, 0x008395, + 0x00839b, 0x00839d, 0x0083c9, 0x0083d0, 0x0083d4, 0x0083dd, 0x0083e5, 0x0083f9, + 0x00840f, 0x008411, 0x008415, 0x026c73, 0x008417, 0x008439, 0x00844a, 0x00844f, + 0x008451, 0x008452, 0x008459, 0x00845a, 0x00845c, 0x026cdd, 0x008465, 0x008476, + 0x008478, 0x00847c, 0x008481, 0x00450d, 0x0084dc, 0x008497, 0x0084a6, 0x0084be, + 0x004508, 0x0084ce, 0x0084cf, 0x0084d3, 0x026e65, 0x0084e7, 0x0084ea, 0x0084ef, + 0x0084f0, 0x0084f1, 0x0084fa, 0x0084fd, 0x00850c, 0x00851b, 0x008524, 0x008525, + 0x00852b, 0x008534, 0x00854f, 0x00856f, 0x004525, 0x004543, 0x00853e, 0x008551, + 0x008553, 0x00855e, 0x008561, 0x008562, 0x026f94, 0x00857b, 0x00857d, 0x00857f, + 0x008581, 0x008586, 0x008593, 0x00859d, 0x00859f, 0x026ff8, + // plane 2 row 87 + 0x026ff6, 0x026ff7, 0x0085b7, 0x0085bc, 0x0085c7, 0x0085ca, 0x0085d8, 0x0085d9, + 0x0085df, 0x0085e1, 0x0085e6, 0x0085f6, 0x008600, 0x008611, 0x00861e, 0x008621, + 0x008624, 0x008627, 0x02710d, 0x008639, 0x00863c, 0x027139, 0x008640, 0x00fa20, + 0x008653, 0x008656, 0x00866f, 0x008677, 0x00867a, 0x008687, 0x008689, 0x00868d, + 0x008691, 0x00869c, 0x00869d, 0x0086a8, 0x00fa21, 0x0086b1, 0x0086b3, 0x0086c1, + 0x0086c3, 0x0086d1, 0x0086d5, 0x0086d7, 0x0086e3, 0x0086e6, 0x0045b8, 0x008705, + 0x008707, 0x00870e, 0x008710, 0x008713, 0x008719, 0x00871f, 0x008721, 0x008723, + 0x008731, 0x00873a, 0x00873e, 0x008740, 0x008743, 0x008751, 0x008758, 0x008764, + 0x008765, 0x008772, 0x00877c, 0x0273db, 0x0273da, 0x0087a7, 0x008789, 0x00878b, + 0x008793, 0x0087a0, 0x0273fe, 0x0045e5, 0x0087be, 0x027410, 0x0087c1, 0x0087ce, + 0x0087f5, 0x0087df, 0x027449, 0x0087e3, 0x0087e5, 0x0087e6, 0x0087ea, 0x0087eb, + 0x0087ed, 0x008801, 0x008803, 0x00880b, 0x008813, 0x008828, + // plane 2 row 88 + 0x00882e, 0x008832, 0x00883c, 0x00460f, 0x00884a, 0x008858, 0x00885f, 0x008864, + 0x027615, 0x027614, 0x008869, 0x027631, 0x00886f, 0x0088a0, 0x0088bc, 0x0088bd, + 0x0088be, 0x0088c0, 0x0088d2, 0x027693, 0x0088d1, 0x0088d3, 0x0088db, 0x0088f0, + 0x0088f1, 0x004641, 0x008901, 0x02770e, 0x008937, 0x027723, 0x008942, 0x008945, + 0x008949, 0x027752, 0x004665, 0x008962, 0x008980, 0x008989, 0x008990, 0x00899f, + 0x0089b0, 0x0089b7, 0x0089d6, 0x0089d8, 0x0089eb, 0x0046a1, 0x0089f1, 0x0089f3, + 0x0089fd, 0x0089ff, 0x0046af, 0x008a11, 0x008a14, 0x027985, 0x008a21, 0x008a35, + 0x008a3e, 0x008a45, 0x008a4d, 0x008a58, 0x008aae, 0x008a90, 0x008ab7, 0x008abe, + 0x008ad7, 0x008afc, 0x027a84, 0x008b0a, 0x008b05, 0x008b0d, 0x008b1c, 0x008b1f, + 0x008b2d, 0x008b43, 0x00470c, 0x008b51, 0x008b5e, 0x008b76, 0x008b7f, 0x008b81, + 0x008b8b, 0x008b94, 0x008b95, 0x008b9c, 0x008b9e, 0x008c39, 0x027bb3, 0x008c3d, + 0x027bbe, 0x027bc7, 0x008c45, 0x008c47, 0x008c4f, 0x008c54, + // plane 2 row 89 + 0x008c57, 0x008c69, 0x008c6d, 0x008c73, 0x027cb8, 0x008c93, 0x008c92, 0x008c99, + 0x004764, 0x008c9b, 0x008ca4, 0x008cd6, 0x008cd5, 0x008cd9, 0x027da0, 0x008cf0, + 0x008cf1, 0x027e10, 0x008d09, 0x008d0e, 0x008d6c, 0x008d84, 0x008d95, 0x008da6, + 0x027fb7, 0x008dc6, 0x008dc8, 0x008dd9, 0x008dec, 0x008e0c, 0x0047fd, 0x008dfd, + 0x008e06, 0x02808a, 0x008e14, 0x008e16, 0x008e21, 0x008e22, 0x008e27, 0x0280bb, + 0x004816, 0x008e36, 0x008e39, 0x008e4b, 0x008e54, 0x008e62, 0x008e6c, 0x008e6d, + 0x008e6f, 0x008e98, 0x008e9e, 0x008eae, 0x008eb3, 0x008eb5, 0x008eb6, 0x008ebb, + 0x028282, 0x008ed1, 0x008ed4, 0x00484e, 0x008ef9, 0x0282f3, 0x008f00, 0x008f08, + 0x008f17, 0x008f2b, 0x008f40, 0x008f4a, 0x008f58, 0x02840c, 0x008fa4, 0x008fb4, + 0x00fa66, 0x008fb6, 0x028455, 0x008fc1, 0x008fc6, 0x00fa24, 0x008fca, 0x008fcd, + 0x008fd3, 0x008fd5, 0x008fe0, 0x008ff1, 0x008ff5, 0x008ffb, 0x009002, 0x00900c, + 0x009037, 0x02856b, 0x009043, 0x009044, 0x00905d, 0x0285c8, + // plane 2 row 90 + 0x0285c9, 0x009085, 0x00908c, 0x009090, 0x00961d, 0x0090a1, 0x0048b5, 0x0090b0, + 0x0090b6, 0x0090c3, 0x0090c8, 0x0286d7, 0x0090dc, 0x0090df, 0x0286fa, 0x0090f6, + 0x0090f2, 0x009100, 0x0090eb, 0x0090fe, 0x0090ff, 0x009104, 0x009106, 0x009118, + 0x00911c, 0x00911e, 0x009137, 0x009139, 0x00913a, 0x009146, 0x009147, 0x009157, + 0x009159, 0x009161, 0x009164, 0x009174, 0x009179, 0x009185, 0x00918e, 0x0091a8, + 0x0091ae, 0x0091b3, 0x0091b6, 0x0091c3, 0x0091c4, 0x0091da, 0x028949, 0x028946, + 0x0091ec, 0x0091ee, 0x009201, 0x00920a, 0x009216, 0x009217, 0x02896b, 0x009233, + 0x009242, 0x009247, 0x00924a, 0x00924e, 0x009251, 0x009256, 0x009259, 0x009260, + 0x009261, 0x009265, 0x009267, 0x009268, 0x028987, 0x028988, 0x00927c, 0x00927d, + 0x00927f, 0x009289, 0x00928d, 0x009297, 0x009299, 0x00929f, 0x0092a7, 0x0092ab, + 0x0289ba, 0x0289bb, 0x0092b2, 0x0092bf, 0x0092c0, 0x0092c6, 0x0092ce, 0x0092d0, + 0x0092d7, 0x0092d9, 0x0092e5, 0x0092e7, 0x009311, 0x028a1e, + // plane 2 row 91 + 0x028a29, 0x0092f7, 0x0092f9, 0x0092fb, 0x009302, 0x00930d, 0x009315, 0x00931d, + 0x00931e, 0x009327, 0x009329, 0x028a71, 0x028a43, 0x009347, 0x009351, 0x009357, + 0x00935a, 0x00936b, 0x009371, 0x009373, 0x0093a1, 0x028a99, 0x028acd, 0x009388, + 0x00938b, 0x00938f, 0x00939e, 0x0093f5, 0x028ae4, 0x028add, 0x0093f1, 0x0093c1, + 0x0093c7, 0x0093dc, 0x0093e2, 0x0093e7, 0x009409, 0x00940f, 0x009416, 0x009417, + 0x0093fb, 0x009432, 0x009434, 0x00943b, 0x009445, 0x028bc1, 0x028bef, 0x00946d, + 0x00946f, 0x009578, 0x009579, 0x009586, 0x00958c, 0x00958d, 0x028d10, 0x0095ab, + 0x0095b4, 0x028d71, 0x0095c8, 0x028dfb, 0x028e1f, 0x00962c, 0x009633, 0x009634, + 0x028e36, 0x00963c, 0x009641, 0x009661, 0x028e89, 0x009682, 0x028eeb, 0x00969a, + 0x028f32, 0x0049e7, 0x0096a9, 0x0096af, 0x0096b3, 0x0096ba, 0x0096bd, 0x0049fa, + 0x028ff8, 0x0096d8, 0x0096da, 0x0096dd, 0x004a04, 0x009714, 0x009723, 0x004a29, + 0x009736, 0x009741, 0x009747, 0x009755, 0x009757, 0x00975b, + // plane 2 row 92 + 0x00976a, 0x0292a0, 0x0292b1, 0x009796, 0x00979a, 0x00979e, 0x0097a2, 0x0097b1, + 0x0097b2, 0x0097be, 0x0097cc, 0x0097d1, 0x0097d4, 0x0097d8, 0x0097d9, 0x0097e1, + 0x0097f1, 0x009804, 0x00980d, 0x00980e, 0x009814, 0x009816, 0x004abc, 0x029490, + 0x009823, 0x009832, 0x009833, 0x009825, 0x009847, 0x009866, 0x0098ab, 0x0098ad, + 0x0098b0, 0x0295cf, 0x0098b7, 0x0098b8, 0x0098bb, 0x0098bc, 0x0098bf, 0x0098c2, + 0x0098c7, 0x0098cb, 0x0098e0, 0x02967f, 0x0098e1, 0x0098e3, 0x0098e5, 0x0098ea, + 0x0098f0, 0x0098f1, 0x0098f3, 0x009908, 0x004b3b, 0x0296f0, 0x009916, 0x009917, + 0x029719, 0x00991a, 0x00991b, 0x00991c, 0x029750, 0x009931, 0x009932, 0x009933, + 0x00993a, 0x00993b, 0x00993c, 0x009940, 0x009941, 0x009946, 0x00994d, 0x00994e, + 0x00995c, 0x00995f, 0x009960, 0x0099a3, 0x0099a6, 0x0099b9, 0x0099bd, 0x0099bf, + 0x0099c3, 0x0099c9, 0x0099d4, 0x0099d9, 0x0099de, 0x0298c6, 0x0099f0, 0x0099f9, + 0x0099fc, 0x009a0a, 0x009a11, 0x009a16, 0x009a1a, 0x009a20, + // plane 2 row 93 + 0x009a31, 0x009a36, 0x009a44, 0x009a4c, 0x009a58, 0x004bc2, 0x009aaf, 0x004bca, + 0x009ab7, 0x004bd2, 0x009ab9, 0x029a72, 0x009ac6, 0x009ad0, 0x009ad2, 0x009ad5, + 0x004be8, 0x009adc, 0x009ae0, 0x009ae5, 0x009ae9, 0x009b03, 0x009b0c, 0x009b10, + 0x009b12, 0x009b16, 0x009b1c, 0x009b2b, 0x009b33, 0x009b3d, 0x004c20, 0x009b4b, + 0x009b63, 0x009b65, 0x009b6b, 0x009b6c, 0x009b73, 0x009b76, 0x009b77, 0x009ba6, + 0x009bac, 0x009bb1, 0x029ddb, 0x029e3d, 0x009bb2, 0x009bb8, 0x009bbe, 0x009bc7, + 0x009bf3, 0x009bd8, 0x009bdd, 0x009be7, 0x009bea, 0x009beb, 0x009bef, 0x009bee, + 0x029e15, 0x009bfa, 0x029e8a, 0x009bf7, 0x029e49, 0x009c16, 0x009c18, 0x009c19, + 0x009c1a, 0x009c1d, 0x009c22, 0x009c27, 0x009c29, 0x009c2a, 0x029ec4, 0x009c31, + 0x009c36, 0x009c37, 0x009c45, 0x009c5c, 0x029ee9, 0x009c49, 0x009c4a, 0x029edb, + 0x009c54, 0x009c58, 0x009c5b, 0x009c5d, 0x009c5f, 0x009c69, 0x009c6a, 0x009c6b, + 0x009c6d, 0x009c6e, 0x009c70, 0x009c72, 0x009c75, 0x009c7a, + // plane 2 row 94 + 0x009ce6, 0x009cf2, 0x009d0b, 0x009d02, 0x029fce, 0x009d11, 0x009d17, 0x009d18, + 0x02a02f, 0x004cc4, 0x02a01a, 0x009d32, 0x004cd1, 0x009d42, 0x009d4a, 0x009d5f, + 0x009d62, 0x02a0f9, 0x009d69, 0x009d6b, 0x02a082, 0x009d73, 0x009d76, 0x009d77, + 0x009d7e, 0x009d84, 0x009d8d, 0x009d99, 0x009da1, 0x009dbf, 0x009db5, 0x009db9, + 0x009dbd, 0x009dc3, 0x009dc7, 0x009dc9, 0x009dd6, 0x009dda, 0x009ddf, 0x009de0, + 0x009de3, 0x009df4, 0x004d07, 0x009e0a, 0x009e02, 0x009e0d, 0x009e19, 0x009e1c, + 0x009e1d, 0x009e7b, 0x022218, 0x009e80, 0x009e85, 0x009e9b, 0x009ea8, 0x02a38c, + 0x009ebd, 0x02a437, 0x009edf, 0x009ee7, 0x009eee, 0x009eff, 0x009f02, 0x004d77, + 0x009f03, 0x009f17, 0x009f19, 0x009f2f, 0x009f37, 0x009f3a, 0x009f3d, 0x009f41, + 0x009f45, 0x009f46, 0x009f53, 0x009f55, 0x009f58, 0x02a5f1, 0x009f5d, 0x02a602, + 0x009f69, 0x02a61a, 0x009f6d, 0x009f70, 0x009f75, 0x02a6b2, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd + }; + + /// Unicode registered symbols + enum UnicodeSymbol { + /// Unrecognized character (Geta character) + UNICODE_BAD_SEQUENCE = 0xfffd + }; + + // EUC-JP decoder + std::string decode_eucjp(std::string_view src) { + unsigned int len = 0; + std::u32string utf32_str; + utf32_str.resize(src.size()); + + // decoding + for (size_t i = 0; i < src.size(); ++i, ++len) { + unsigned char b1, b2, b3; + if (len >= utf32_str.size()) { + utf32_str.resize(utf32_str.size() * 2); + } + b1 = src[i]; + // end of text + if (b1 == 0x00) break; + // 1 byte sequence + if (b1 <= 0x7f) utf32_str[len] = (int)b1; + // 3 bytes sequence (JIS X 0213 plane 2) + else if (b1 == 0x8f) { + if ((i += 2) >= src.size()) break; + b2 = src[i - 1], b3 = src[i]; + if (0xa1 <= b2 && b2 <= 0xfe && 0xa1 <= b3 && b3 <= 0xfe) + utf32_str[len] = Encoding::jisx0213_2_unicode[(b2 - 0xa1 + 94) * 94 + (b3 - 0xa1)]; + // bad sequence + else { + utf32_str[len] = Encoding::UNICODE_BAD_SEQUENCE; + i -= 2; + } + } + // 2 bytes sequence + else /* b1 >= 0x80 */ { + if (++i >= src.size()) break; + b2 = src[i]; + // JIS X 0201 kana + if (b1 == 0x8e) utf32_str[len] = Encoding::jisx0201_2_unicode[b2]; + // JIS X 0213 plane 1 + else if (0xa1 <= b1 && b1 <= 0xfe && 0xa1 <= b2 && b2 <= 0xfe) + utf32_str[len] = Encoding::jisx0213_2_unicode[(b1 - 0xa1) * 94 + (b2 - 0xa1)]; + // bad sequence + else { + utf32_str[len] = Encoding::UNICODE_BAD_SEQUENCE; + --i; + } + } + } + utf32_str.resize(len); + + std::wstring_convert, char32_t> conv; + std::string utf8_str = conv.to_bytes(utf32_str); + + return utf8_str; + } +} diff --git a/src/euc-jp.h b/src/euc-jp.h new file mode 100644 index 0000000..9ee246f --- /dev/null +++ b/src/euc-jp.h @@ -0,0 +1,11 @@ +#ifndef __EUC_JP_H__ +#define __EUC_JP_H__ + +#include +#include + +namespace Encoding { + std::string decode_eucjp(std::string_view src); +} + +#endif diff --git a/src/flash.cpp b/src/flash.cpp new file mode 100644 index 0000000..9211bc0 --- /dev/null +++ b/src/flash.cpp @@ -0,0 +1,137 @@ +#include +#include +#include "../portultra/ultra64.h" +#include "../portultra/multilibultra.hpp" +#include "recomp.h" + +constexpr uint32_t flash_size = 1024 * 1024 / 8; // 1Mbit +constexpr uint32_t page_size = 128; +constexpr uint32_t pages_per_sector = 128; +constexpr uint32_t sector_size = page_size * pages_per_sector; +constexpr uint32_t sector_count = flash_size / sector_size; + +void save_write_ptr(const void* in, uint32_t offset, uint32_t count); +void save_write(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count); +void save_read(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count); +void save_clear(uint32_t start, uint32_t size); + +std::array write_buffer; + +extern "C" void osFlashInit_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = Multilibultra::flash_handle; +} + +extern "C" void osFlashReadStatus_recomp(uint8_t * rdram, recomp_context * ctx) { + PTR(u8) flash_status = ctx->r4; + + MEM_B(0, flash_status) = 0; +} + +extern "C" void osFlashReadId_recomp(uint8_t * rdram, recomp_context * ctx) { + PTR(u32) flash_type = ctx->r4; + PTR(u32) flash_maker = ctx->r5; + + MEM_B(0, flash_type) = 0; + MEM_B(0, flash_maker) = 0; +} + +extern "C" void osFlashClearStatus_recomp(uint8_t * rdram, recomp_context * ctx) { + +} + +extern "C" void osFlashAllErase_recomp(uint8_t * rdram, recomp_context * ctx) { + save_clear(0, Multilibultra::save_size); + + ctx->r2 = 0; +} + +extern "C" void osFlashAllEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) { + save_clear(0, Multilibultra::save_size); + + ctx->r2 = 0; +} + +extern "C" void osFlashSectorErase_recomp(uint8_t * rdram, recomp_context * ctx) { + uint32_t page_num = (uint32_t)ctx->r4; + uint32_t sector_num = page_num / sector_size; + + // Prevent out of bounds erase + if (sector_num >= sector_size) { + ctx->r2 = -1; + return; + } + + save_clear(sector_num * sector_size, sector_size); + + ctx->r2 = 0; +} + +extern "C" void osFlashSectorEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) { + uint32_t page_num = (uint32_t)ctx->r4; + uint32_t sector_num = page_num / sector_size; + + // Prevent out of bounds erase + if (sector_num >= sector_size) { + ctx->r2 = -1; + return; + } + + save_clear(sector_num * sector_size, sector_size); + + ctx->r2 = 0; +} + +extern "C" void osFlashCheckEraseEnd_recomp(uint8_t * rdram, recomp_context * ctx) { + // All erases are blocking in this implementation, so this should always return OK. + ctx->r2 = 0; // FLASH_STATUS_ERASE_OK +} + +extern "C" void osFlashWriteBuffer_recomp(uint8_t * rdram, recomp_context * ctx) { + OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r4); + int32_t pri = ctx->r5; + PTR(void) dramAddr = ctx->r6; + PTR(OSMesgQueue) mq = ctx->r7; + + // Copy the input data into the write buffer + for (size_t i = 0; i < page_size; i++) { + write_buffer[i] = MEM_B(i, dramAddr); + } + + // Send the message indicating write completion + osSendMesg(PASS_RDRAM mq, 0, OS_MESG_NOBLOCK); + + ctx->r2 = 0; +} + +extern "C" void osFlashWriteArray_recomp(uint8_t * rdram, recomp_context * ctx) { + uint32_t page_num = ctx->r4; + + // Copy the write buffer into the save file + save_write_ptr(write_buffer.data(), page_num * page_size, page_size); + + ctx->r2 = 0; +} + +extern "C" void osFlashReadArray_recomp(uint8_t * rdram, recomp_context * ctx) { + OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r4); + int32_t pri = ctx->r5; + uint32_t page_num = ctx->r6; + PTR(void) dramAddr = ctx->r7; + uint32_t n_pages = MEM_W(0x10, ctx->r29); + PTR(OSMesgQueue) mq = MEM_W(0x14, ctx->r29); + + uint32_t offset = page_num * page_size; + uint32_t count = n_pages * page_size; + + // Read from the save file into the provided buffer + save_read(PASS_RDRAM dramAddr, offset, count); + + // Send the message indicating read completion + osSendMesg(PASS_RDRAM mq, 0, OS_MESG_NOBLOCK); + + ctx->r2 = 0; +} + +extern "C" void osFlashChange_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} diff --git a/src/math_routines.cpp b/src/math_routines.cpp new file mode 100644 index 0000000..e0f82aa --- /dev/null +++ b/src/math_routines.cpp @@ -0,0 +1,80 @@ +#include "../portultra/multilibultra.hpp" +#include "recomp.h" + + +extern "C" void __udivdi3_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); + uint64_t ret = a / b; + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +} + +extern "C" void __divdi3_recomp(uint8_t * rdram, recomp_context * ctx) { + int64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + int64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); + int64_t ret = a / b; + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +} + +extern "C" void __umoddi3_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); + uint64_t ret = a % b; + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +} + +extern "C" void __ull_div_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); + uint64_t ret = a / b; + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +} + +extern "C" void __ll_div_recomp(uint8_t * rdram, recomp_context * ctx) { + int64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + int64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); + int64_t ret = a / b; + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +} + +extern "C" void __ll_mul_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); + uint64_t ret = a * b; + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +} + +extern "C" void __ull_rem_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); + uint64_t ret = a % b; + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +} + +extern "C" void __ull_to_d_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + double ret = (double)a; + + ctx->f0.d = ret; +} + +extern "C" void __ull_to_f_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + float ret = (float)a; + + ctx->f0.fl = ret; +} diff --git a/src/overlays.cpp b/src/overlays.cpp new file mode 100644 index 0000000..23f6eea --- /dev/null +++ b/src/overlays.cpp @@ -0,0 +1,110 @@ +#include +#include +#include +#include "recomp.h" +#include "../RecompiledFuncs/recomp_overlays.inl" + +constexpr size_t num_code_sections = ARRLEN(section_table); + +// SectionTableEntry sections[] defined in recomp_overlays.inl + +struct LoadedSection { + int32_t loaded_ram_addr; + size_t section_table_index; + + bool operator<(const LoadedSection& rhs) { + return loaded_ram_addr < rhs.loaded_ram_addr; + } +}; + +std::vector loaded_sections{}; +std::unordered_map func_map{}; + +void load_overlay(size_t section_table_index, int32_t ram) { + const SectionTableEntry& section = section_table[section_table_index]; + for (size_t function_index = 0; function_index < section.num_funcs; function_index++) { + const FuncEntry& func = section.funcs[function_index]; + func_map[ram + func.offset] = func.func; + } + loaded_sections.emplace_back(ram, section_table_index); + section_addresses[section.index] = ram; +} + + +extern "C" { +int32_t section_addresses[num_sections]; +} + +extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size) { + // Search for the first section that's included in the loaded rom range + // Sections were sorted by `init_overlays` so we can use the bounds functions + auto lower = std::lower_bound(§ion_table[0], §ion_table[num_code_sections], rom, + [](const SectionTableEntry& entry, uint32_t addr) { + return entry.rom_addr < addr; + } + ); + auto upper = std::upper_bound(§ion_table[0], §ion_table[num_code_sections], (uint32_t)(rom + size), + [](uint32_t addr, const SectionTableEntry& entry) { + return addr < entry.size + entry.rom_addr; + } + ); + // Load the overlays that were found + for (auto it = lower; it != upper; ++it) { + load_overlay(std::distance(§ion_table[0], it), it->rom_addr - rom + ram_addr); + } +} + +extern "C" void unload_overlays(int32_t ram_addr, uint32_t size) { + for (auto it = loaded_sections.begin(); it != loaded_sections.end();) { + const auto& section = section_table[it->section_table_index]; + + // Check if the unloaded region overlaps with the loaded section + if (ram_addr < (it->loaded_ram_addr + section.size) && (ram_addr + size) >= it->loaded_ram_addr) { + // Check if the section isn't entirely in the loaded region + if (ram_addr > it->loaded_ram_addr || (ram_addr + size) < (it->loaded_ram_addr + section.size)) { + fprintf(stderr, + "Cannot partially unload section\n" + " rom: 0x%08X size: 0x%08X loaded_addr: 0x%08X\n" + " unloaded_ram: 0x%08X unloaded_size : 0x%08X\n", + section.rom_addr, section.size, it->loaded_ram_addr, ram_addr, size); + std::exit(EXIT_FAILURE); + } + // Determine where each function was loaded to and remove that entry from the function map + for (size_t func_index = 0; func_index < section.num_funcs; func_index++) { + const auto& func = section.funcs[func_index]; + uint32_t func_address = func.offset + it->loaded_ram_addr; + func_map.erase(func_address); + } + // Reset the section's address in the address table + section_addresses[section.index] = 0; + // Remove the section from the loaded section map + it = loaded_sections.erase(it); + // Skip incrementing the iterator + continue; + } + ++it; + } +} + +void init_overlays() { + for (size_t section_index = 0; section_index < num_code_sections; section_index++) { + section_addresses[section_table[section_index].index] = section_table[section_index].ram_addr; + } + + // Sort the executable sections by rom address + std::sort(§ion_table[0], §ion_table[num_code_sections], + [](const SectionTableEntry& a, const SectionTableEntry& b) { + return a.rom_addr < b.rom_addr; + } + ); +} + +extern "C" recomp_func_t * get_function(int32_t addr) { + auto func_find = func_map.find(addr); + if (func_find == func_map.end()) { + fprintf(stderr, "Failed to find function at 0x%08X\n", addr); + std::exit(EXIT_FAILURE); + } + return func_find->second; +} + diff --git a/src/pak.cpp b/src/pak.cpp new file mode 100644 index 0000000..777ece6 --- /dev/null +++ b/src/pak.cpp @@ -0,0 +1,31 @@ +#include "recomp.h" +#include "../portultra/ultra64.h" +#include "../portultra/multilibultra.hpp" + +extern "C" void osPfsInitPak_recomp(uint8_t * rdram, recomp_context* ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} + +extern "C" void osPfsFreeBlocks_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} + +extern "C" void osPfsAllocateFile_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} + +extern "C" void osPfsDeleteFile_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} + +extern "C" void osPfsFileState_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} + +extern "C" void osPfsFindFile_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} + +extern "C" void osPfsReadWriteFile_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} diff --git a/src/pi.cpp b/src/pi.cpp new file mode 100644 index 0000000..49bc898 --- /dev/null +++ b/src/pi.cpp @@ -0,0 +1,193 @@ +#include +#include +#include +#include "recomp.h" +#include "../portultra/ultra64.h" +#include "../portultra/multilibultra.hpp" + +// Flashram occupies the same physical address as sram, but that issue is avoided because libultra exposes +// a high-level interface for flashram. Because that high-level interface is reimplemented, low level accesses +// that involve physical addresses don't need to be handled for flashram. +constexpr uint32_t sram_base = 0x08000000; +constexpr uint32_t rom_base = 0x10000000; + +constexpr uint32_t k1_to_phys(uint32_t addr) { + return addr & 0x1FFFFFFF; +} + +constexpr uint32_t phys_to_k1(uint32_t addr) { + return addr | 0xA0000000; +} + +extern std::unique_ptr rom; +extern size_t rom_size; + +extern "C" void osCartRomInit_recomp(uint8_t* rdram, recomp_context* ctx) { + OSPiHandle* handle = TO_PTR(OSPiHandle, Multilibultra::cart_handle); + handle->type = 0; // cart + handle->baseAddress = phys_to_k1(rom_base); + handle->domain = 0; + + ctx->r2 = (gpr)Multilibultra::cart_handle; +} + +extern "C" void osCreatePiManager_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} + +void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes) { + // TODO use word copies when possible + uint8_t* rom_addr = rom.get() + physical_addr - rom_base; + for (size_t i = 0; i < num_bytes; i++) { + MEM_B(i, ram_address) = *rom_addr; + rom_addr++; + } +} + +std::array save_buffer; +const char save_filename[] = "save.bin"; + +void update_save_file() { + std::ofstream save_file{ save_filename, std::ios_base::binary }; + + if (save_file.good()) { + save_file.write(save_buffer.data(), save_buffer.size()); + } else { + fprintf(stderr, "Failed to save!\n"); + std::exit(EXIT_FAILURE); + } +} + +void save_write_ptr(const void* in, uint32_t offset, uint32_t count) { + memcpy(&save_buffer[offset], in, count); + update_save_file(); +} + +void save_write(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count) { + for (uint32_t i = 0; i < count; i++) { + save_buffer[offset + i] = MEM_B(i, rdram_address); + } + update_save_file(); +} + +void save_read(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count) { + for (size_t i = 0; i < count; i++) { + MEM_B(i, rdram_address) = save_buffer[offset + i]; + } +} + +void save_clear(uint32_t start, uint32_t size) { + std::fill_n(save_buffer.begin() + start, size, 0); + std::ofstream save_file{ save_filename, std::ios_base::binary }; + + if (save_file.good()) { + save_file.write(save_buffer.data(), save_buffer.size()); + } else { + fprintf(stderr, "Failed to save!\n"); + std::exit(EXIT_FAILURE); + } +} + +void Multilibultra::save_init() { + std::ifstream save_file{ save_filename, std::ios_base::binary }; + + if (save_file.good()) { + save_file.read(save_buffer.data(), save_buffer.size()); + } else { + save_buffer.fill(0); + } +} + +void do_dma(uint8_t* rdram, PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_addr, uint32_t size, uint32_t direction) { + // TODO asynchronous transfer + // TODO implement unaligned DMA correctly + if (direction == 0) { + if (physical_addr >= rom_base) { + // read cart rom + do_rom_read(rdram, rdram_address, physical_addr, size); + + // Send a message to the mq to indicate that the transfer completed + osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK); + } else if (physical_addr >= sram_base) { + // read sram + save_read(rdram, rdram_address, physical_addr - sram_base, size); + + // Send a message to the mq to indicate that the transfer completed + osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK); + } else { + fprintf(stderr, "[WARN] PI DMA read from unknown region, phys address 0x%08X\n", physical_addr); + } + } else { + if (physical_addr >= rom_base) { + // write cart rom + throw std::runtime_error("ROM DMA write unimplemented"); + } else if (physical_addr >= sram_base) { + // write sram + save_write(rdram, rdram_address, physical_addr - sram_base, size); + + // Send a message to the mq to indicate that the transfer completed + osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK); + } else { + fprintf(stderr, "[WARN] PI DMA write to unknown region, phys address 0x%08X\n", physical_addr); + } + } +} + +extern "C" void osPiStartDma_recomp(uint8_t* rdram, recomp_context* ctx) { + uint32_t mb = ctx->r4; + uint32_t pri = ctx->r5; + uint32_t direction = ctx->r6; + uint32_t devAddr = ctx->r7; + gpr dramAddr = MEM_W(0x10, ctx->r29); + uint32_t size = MEM_W(0x14, ctx->r29); + PTR(OSMesgQueue) mq = MEM_W(0x18, ctx->r29); + uint32_t physical_addr = k1_to_phys(devAddr); + + debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size); + + do_dma(rdram, mq, dramAddr, physical_addr, size, direction); + + ctx->r2 = 0; +} + +extern "C" void osEPiStartDma_recomp(uint8_t* rdram, recomp_context* ctx) { + OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4); + OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r5); + uint32_t direction = ctx->r6; + uint32_t devAddr = handle->baseAddress | mb->devAddr; + gpr dramAddr = mb->dramAddr; + uint32_t size = mb->size; + PTR(OSMesgQueue) mq = mb->hdr.retQueue; + uint32_t physical_addr = k1_to_phys(devAddr); + + debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size); + + do_dma(rdram, mq, dramAddr, physical_addr, size, direction); + + ctx->r2 = 0; +} + +extern "C" void osEPiReadIo_recomp(uint8_t * rdram, recomp_context * ctx) { + OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4); + uint32_t devAddr = handle->baseAddress | ctx->r5; + gpr dramAddr = ctx->r6; + uint32_t physical_addr = k1_to_phys(devAddr); + + if (physical_addr > rom_base) { + // cart rom + do_rom_read(rdram, dramAddr, physical_addr, sizeof(uint32_t)); + } else { + // sram + assert(false && "SRAM ReadIo unimplemented"); + } + + ctx->r2 = 0; +} + +extern "C" void osPiGetStatus_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 0; +} + +extern "C" void osPiRawStartDma_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 0; +} diff --git a/src/portultra_stubs.cpp b/src/portultra_stubs.cpp new file mode 100644 index 0000000..b499ad3 --- /dev/null +++ b/src/portultra_stubs.cpp @@ -0,0 +1,45 @@ +#include "../portultra/ultra64.h" +#include "../portultra/multilibultra.hpp" +#include "recomp.h" + +// None of these functions need to be reimplemented, so stub them out +extern "C" void osUnmapTLBAll_recomp(uint8_t * rdram, recomp_context * ctx) { + // TODO this will need to be implemented in the future for any games that actually use the TLB +} + +extern "C" void osVoiceInit_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 11; // CONT_ERR_DEVICE +} + +extern "C" void osVoiceSetWord_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + +extern "C" void osVoiceCheckWord_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + +extern "C" void osVoiceStopReadData_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + +extern "C" void osVoiceMaskDictionary_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + +extern "C" void osVoiceStartReadData_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + +extern "C" void osVoiceControlGain_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + +extern "C" void osVoiceGetReadData_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + +extern "C" void osVoiceClearDictionary_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + diff --git a/src/portultra_translation.cpp b/src/portultra_translation.cpp new file mode 100644 index 0000000..f2f9df2 --- /dev/null +++ b/src/portultra_translation.cpp @@ -0,0 +1,158 @@ +#include +#include "../portultra/ultra64.h" +#include "../portultra/multilibultra.hpp" +#include "recomp.h" + +extern "C" void osInitialize_recomp(uint8_t * rdram, recomp_context * ctx) { + osInitialize(); +} + +extern "C" void __osInitialize_common_recomp(uint8_t * rdram, recomp_context * ctx) { + osInitialize(); +} + +extern "C" void osCreateThread_recomp(uint8_t* rdram, recomp_context* ctx) { + osCreateThread(rdram, (int32_t)ctx->r4, (OSId)ctx->r5, (int32_t)ctx->r6, (int32_t)ctx->r7, + (int32_t)MEM_W(0x10, ctx->r29), (OSPri)MEM_W(0x14, ctx->r29)); +} + +extern "C" void osStartThread_recomp(uint8_t* rdram, recomp_context* ctx) { + osStartThread(rdram, (int32_t)ctx->r4); +} + +extern "C" void osStopThread_recomp(uint8_t * rdram, recomp_context * ctx) { + osStopThread(rdram, (int32_t)ctx->r4); +} + +extern "C" void osDestroyThread_recomp(uint8_t * rdram, recomp_context * ctx) { + osDestroyThread(rdram, (int32_t)ctx->r4); +} + +extern "C" void osSetThreadPri_recomp(uint8_t* rdram, recomp_context* ctx) { + osSetThreadPri(rdram, (int32_t)ctx->r4, (OSPri)ctx->r5); +} + +extern "C" void osGetThreadPri_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = osGetThreadPri(rdram, (int32_t)ctx->r4); +} + +extern "C" void osGetThreadId_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = osGetThreadId(rdram, (int32_t)ctx->r4); +} + +extern "C" void osCreateMesgQueue_recomp(uint8_t* rdram, recomp_context* ctx) { + osCreateMesgQueue(rdram, (int32_t)ctx->r4, (int32_t)ctx->r5, (s32)ctx->r6); +} + +extern "C" void osRecvMesg_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = osRecvMesg(rdram, (int32_t)ctx->r4, (int32_t)ctx->r5, (s32)ctx->r6); +} + +extern "C" void osSendMesg_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = osSendMesg(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6); +} + +extern "C" void osJamMesg_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = osJamMesg(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6); +} + +extern "C" void osSetEventMesg_recomp(uint8_t* rdram, recomp_context* ctx) { + osSetEventMesg(rdram, (OSEvent)ctx->r4, (int32_t)ctx->r5, (OSMesg)ctx->r6); +} + +extern "C" void osViSetEvent_recomp(uint8_t * rdram, recomp_context * ctx) { + osViSetEvent(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (u32)ctx->r6); +} + +extern "C" void osGetCount_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = osGetCount(); +} + +extern "C" void osGetTime_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t total_count = osGetTime(); + ctx->r2 = (int32_t)(total_count >> 32); + ctx->r3 = (int32_t)(total_count >> 0); +} + +extern "C" void osSetTimer_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t countdown = ((uint64_t)(ctx->r6) << 32) | ((ctx->r7) & 0xFFFFFFFFu); + uint64_t interval = load_doubleword(rdram, ctx->r29, 0x10); + ctx->r2 = osSetTimer(rdram, (int32_t)ctx->r4, countdown, interval, (int32_t)MEM_W(0x18, ctx->r29), (OSMesg)MEM_W(0x1C, ctx->r29)); +} + +extern "C" void osStopTimer_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = osStopTimer(rdram, (int32_t)ctx->r4); +} + +extern "C" void osVirtualToPhysical_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = osVirtualToPhysical((int32_t)ctx->r2); +} + +extern "C" void osInvalDCache_recomp(uint8_t * rdram, recomp_context * ctx) { + ; +} + +extern "C" void osInvalICache_recomp(uint8_t * rdram, recomp_context * ctx) { + ; +} + +extern "C" void osWritebackDCache_recomp(uint8_t * rdram, recomp_context * ctx) { + ; +} + +extern "C" void osWritebackDCacheAll_recomp(uint8_t * rdram, recomp_context * ctx) { + ; +} + +extern "C" void osSetIntMask_recomp(uint8_t * rdram, recomp_context * ctx) { + ; +} + +extern "C" void __osDisableInt_recomp(uint8_t * rdram, recomp_context * ctx) { + ; +} + +extern "C" void __osRestoreInt_recomp(uint8_t * rdram, recomp_context * ctx) { + ; +} + +extern "C" void __osSetFpcCsr_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 0; +} + +// For the Mario Party games (not working) +//extern "C" void longjmp_recomp(uint8_t * rdram, recomp_context * ctx) { +// RecompJmpBuf* buf = TO_PTR(RecompJmpBuf, ctx->r4); +// +// // Check if this is a buffer that was set up with setjmp +// if (buf->magic == SETJMP_MAGIC) { +// // If so, longjmp to it +// // Setjmp/longjmp does not work across threads, so verify that this buffer was made by this thread +// assert(buf->owner == Multilibultra::this_thread()); +// longjmp(buf->storage->buffer, ctx->r5); +// } else { +// // Otherwise, check if it was one built manually by the game with $ra pointing to a function +// gpr sp = MEM_W(0, ctx->r4); +// gpr ra = MEM_W(4, ctx->r4); +// ctx->r29 = sp; +// recomp_func_t* target = LOOKUP_FUNC(ra); +// if (target == nullptr) { +// fprintf(stderr, "Failed to find function for manual longjmp\n"); +// std::quick_exit(EXIT_FAILURE); +// } +// target(rdram, ctx); +// +// // TODO kill this thread if the target function returns +// assert(false); +// } +//} +// +//#undef setjmp_recomp +//extern "C" void setjmp_recomp(uint8_t * rdram, recomp_context * ctx) { +// fprintf(stderr, "Program called setjmp_recomp\n"); +// std::quick_exit(EXIT_FAILURE); +//} +// +//extern "C" int32_t osGetThreadEx(void) { +// return Multilibultra::this_thread(); +//} diff --git a/src/print.cpp b/src/print.cpp new file mode 100644 index 0000000..a904fa2 --- /dev/null +++ b/src/print.cpp @@ -0,0 +1,70 @@ +#include "../portultra/ultra64.h" +#include "../portultra/multilibultra.hpp" +#include "recomp.h" +#include "euc-jp.h" + +extern "C" void __checkHardware_msp_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 0; +} + +extern "C" void __checkHardware_kmc_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 0; +} + +extern "C" void __checkHardware_isv_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 0; +} + +extern "C" void __osInitialize_msp_recomp(uint8_t * rdram, recomp_context * ctx) { +} + +extern "C" void __osInitialize_kmc_recomp(uint8_t * rdram, recomp_context * ctx) { +} + +extern "C" void __osInitialize_isv_recomp(uint8_t * rdram, recomp_context * ctx) { +} + +extern "C" void isPrintfInit_recomp(uint8_t * rdram, recomp_context * ctx) { +} + +extern "C" void __osRdbSend_recomp(uint8_t * rdram, recomp_context * ctx) { + gpr buf = ctx->r4; + size_t size = ctx->r5; + u32 type = (u32)ctx->r6; + std::unique_ptr to_print = std::make_unique(size + 1); + + for (size_t i = 0; i < size; i++) { + to_print[i] = MEM_B(i, buf); + } + to_print[size] = '\x00'; + + fwrite(to_print.get(), 1, size, stdout); + + ctx->r2 = size; +} + +extern "C" void is_proutSyncPrintf_recomp(uint8_t * rdram, recomp_context * ctx) { + // Buffering to speed up print performance + static std::vector print_buffer; + + gpr buf = ctx->r5; + size_t size = ctx->r6; + + //for (size_t i = 0; i < size; i++) { + // // Add the new character to the buffer + // char cur_char = MEM_B(i, buf); + + // // If the new character is a newline, flush the buffer + // if (cur_char == '\n') { + // std::string utf8_str = Encoding::decode_eucjp(std::string_view{ print_buffer.data(), print_buffer.size() }); + // puts(utf8_str.c_str()); + // print_buffer.clear(); + // } else { + // print_buffer.push_back(cur_char); + // } + //} + + //fwrite(to_print.get(), size, 1, stdout); + + ctx->r2 = 1; +} diff --git a/src/recomp.cpp b/src/recomp.cpp new file mode 100644 index 0000000..957311c --- /dev/null +++ b/src/recomp.cpp @@ -0,0 +1,165 @@ +#ifdef _WIN32 +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include "recomp.h" +#include "../portultra/multilibultra.hpp" + +#ifdef _MSC_VER +inline uint32_t byteswap(uint32_t val) { + return _byteswap_ulong(val); +} +#else +constexpr uint32_t byteswap(uint32_t val) { + return __builtin_bswap32(val); +} +#endif + +extern "C" void _bzero(uint8_t* rdram, recomp_context* ctx) { + gpr start_addr = ctx->r4; + gpr size = ctx->r5; + + for (uint32_t i = 0; i < size; i++) { + MEM_B(start_addr, i) = 0; + } +} + +extern "C" void osGetMemSize_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 8 * 1024 * 1024; +} + +extern "C" void switch_error(const char* func, uint32_t vram, uint32_t jtbl) { + printf("Switch-case out of bounds in %s at 0x%08X for jump table at 0x%08X\n", func, vram, jtbl); + exit(EXIT_FAILURE); +} + +extern "C" void do_break(uint32_t vram) { + printf("Encountered break at original vram 0x%08X\n", vram); + exit(EXIT_FAILURE); +} + +void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t arg) { + recomp_context ctx{}; + ctx.r29 = sp; + ctx.r4 = arg; + recomp_func_t* func = get_function(addr); + func(rdram, &ctx); +} + +void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t dev_address, size_t num_bytes); + +std::unique_ptr rom; +size_t rom_size; + +// Recomp generation functions +extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx); +gpr get_entrypoint_address(); +const char* get_rom_name(); +void init_overlays(); +extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size); +extern "C" void unload_overlays(int32_t ram_addr, uint32_t size); + +#ifdef _WIN32 +#include +#endif + +int main(int argc, char **argv) { + //if (argc != 2) { + // printf("Usage: %s [baserom]\n", argv[0]); + // exit(EXIT_SUCCESS); + //} + +#ifdef _WIN32 + // Set up console output to accept UTF-8 on windows + SetConsoleOutputCP(CP_UTF8); + + // Change to a font that supports Japanese characters + CONSOLE_FONT_INFOEX cfi; + cfi.cbSize = sizeof cfi; + cfi.nFont = 0; + cfi.dwFontSize.X = 0; + cfi.dwFontSize.Y = 16; + cfi.FontFamily = FF_DONTCARE; + cfi.FontWeight = FW_NORMAL; + wcscpy_s(cfi.FaceName, L"NSimSun"); + SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi); +#else + std::setlocale(LC_ALL, "en_US.UTF-8"); +#endif + + { + std::basic_ifstream rom_file{ get_rom_name(), std::ios::binary }; + + size_t iobuf_size = 0x100000; + std::unique_ptr iobuf = std::make_unique(iobuf_size); + rom_file.rdbuf()->pubsetbuf(iobuf.get(), iobuf_size); + + if (!rom_file) { + fprintf(stderr, "Failed to open rom: %s\n", get_rom_name()); + exit(EXIT_FAILURE); + } + + rom_file.seekg(0, std::ios::end); + rom_size = rom_file.tellg(); + rom_file.seekg(0, std::ios::beg); + + rom = std::make_unique(rom_size); + + rom_file.read(rom.get(), rom_size); + + // TODO remove this + // Modify the name in the rom header so RT64 doesn't find it + rom[0x2F] = 'O'; + } + + // Initialize the overlays + init_overlays(); + + // Get entrypoint from recomp function + gpr entrypoint = get_entrypoint_address(); + + // Load overlays in the first 1MB + load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024); + + // Allocate rdram_buffer (16MB to give room for any extra addressable data used by recomp) + std::unique_ptr rdram_buffer = std::make_unique(16 * 1024 * 1024); + std::memset(rdram_buffer.get(), 0, 8 * 1024 * 1024); + recomp_context context{}; + + // Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000) + do_rom_read(rdram_buffer.get(), entrypoint, 0x10001000, 0x100000); + + // Set up stack pointer + context.r29 = 0xFFFFFFFF803FFFF0u; + + // Initialize variables normally set by IPL3 + constexpr int32_t osTvType = 0x80000300; + constexpr int32_t osRomType = 0x80000304; + constexpr int32_t osRomBase = 0x80000308; + constexpr int32_t osResetType = 0x8000030c; + constexpr int32_t osCicId = 0x80000310; + constexpr int32_t osVersion = 0x80000314; + constexpr int32_t osMemSize = 0x80000318; + constexpr int32_t osAppNMIBuffer = 0x8000031c; + uint8_t *rdram = rdram_buffer.get(); + MEM_W(osTvType, 0) = 1; // NTSC + MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base + MEM_W(osResetType, 0) = 0; // cold reset + MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB + + debug_printf("[Recomp] Starting\n"); + + Multilibultra::preinit(rdram_buffer.get(), rom.get()); + + recomp_entrypoint(rdram_buffer.get(), &context); + + debug_printf("[Recomp] Quitting\n"); + + return EXIT_SUCCESS; +} diff --git a/src/rt64_layer.cpp b/src/rt64_layer.cpp new file mode 100644 index 0000000..7acbcfe --- /dev/null +++ b/src/rt64_layer.cpp @@ -0,0 +1,114 @@ +#include +#include + +#include "../portultra/multilibultra.hpp" +#include "rt64_layer.h" +#include "SDL.h" + +static uint8_t DMEM[0x1000]; +static uint8_t IMEM[0x1000]; + +unsigned int MI_INTR_REG = 0; + +unsigned int DPC_START_REG = 0; +unsigned int DPC_END_REG = 0; +unsigned int DPC_CURRENT_REG = 0; +unsigned int DPC_STATUS_REG = 0; +unsigned int DPC_CLOCK_REG = 0; +unsigned int DPC_BUFBUSY_REG = 0; +unsigned int DPC_PIPEBUSY_REG = 0; +unsigned int DPC_TMEM_REG = 0; + +unsigned int VI_STATUS_REG = 0; +unsigned int VI_ORIGIN_REG = 0; +unsigned int VI_WIDTH_REG = 0; +unsigned int VI_INTR_REG = 0; +unsigned int VI_V_CURRENT_LINE_REG = 0; +unsigned int VI_TIMING_REG = 0; +unsigned int VI_V_SYNC_REG = 0; +unsigned int VI_H_SYNC_REG = 0; +unsigned int VI_LEAP_REG = 0; +unsigned int VI_H_START_REG = 0; +unsigned int VI_V_START_REG = 0; +unsigned int VI_V_BURST_REG = 0; +unsigned int VI_X_SCALE_REG = 0; +unsigned int VI_Y_SCALE_REG = 0; + +unsigned int SP_STATUS_REG = 0; +unsigned int RDRAM_SIZE = 0x800000; + +#define GET_FUNC(lib, name) \ + name = (decltype(name))GetProcAddress(lib, #name) + +void dummy_check_interrupts() { + +} + +void RT64Init(uint8_t* rom, uint8_t* rdram) { + // Dynamic loading + //auto RT64 = LoadLibrary("RT64.dll"); + //if (RT64 == 0) { + // fprintf(stdout, "Failed to load RT64\n"); + // std::exit(EXIT_FAILURE); + //} + //GET_FUNC(RT64, InitiateGFX); + //GET_FUNC(RT64, ProcessRDPList); + //GET_FUNC(RT64, ProcessDList); + //GET_FUNC(RT64, UpdateScreen); + + GFX_INFO gfx_info{}; + gfx_info.HEADER = rom; + gfx_info.RDRAM = rdram; + gfx_info.DMEM = DMEM; + gfx_info.IMEM = IMEM; + + gfx_info.MI_INTR_REG = &MI_INTR_REG; + + gfx_info.DPC_START_REG = &DPC_START_REG; + gfx_info.DPC_END_REG = &DPC_END_REG; + gfx_info.DPC_CURRENT_REG = &DPC_CURRENT_REG; + gfx_info.DPC_STATUS_REG = &DPC_STATUS_REG; + gfx_info.DPC_CLOCK_REG = &DPC_CLOCK_REG; + gfx_info.DPC_BUFBUSY_REG = &DPC_BUFBUSY_REG; + gfx_info.DPC_PIPEBUSY_REG = &DPC_PIPEBUSY_REG; + gfx_info.DPC_TMEM_REG = &DPC_TMEM_REG; + + gfx_info.VI_STATUS_REG = &VI_STATUS_REG; + gfx_info.VI_ORIGIN_REG = &VI_ORIGIN_REG; + gfx_info.VI_WIDTH_REG = &VI_WIDTH_REG; + gfx_info.VI_INTR_REG = &VI_INTR_REG; + gfx_info.VI_V_CURRENT_LINE_REG = &VI_V_CURRENT_LINE_REG; + gfx_info.VI_TIMING_REG = &VI_TIMING_REG; + gfx_info.VI_V_SYNC_REG = &VI_V_SYNC_REG; + gfx_info.VI_H_SYNC_REG = &VI_H_SYNC_REG; + gfx_info.VI_LEAP_REG = &VI_LEAP_REG; + gfx_info.VI_H_START_REG = &VI_H_START_REG; + gfx_info.VI_V_START_REG = &VI_V_START_REG; + gfx_info.VI_V_BURST_REG = &VI_V_BURST_REG; + gfx_info.VI_X_SCALE_REG = &VI_X_SCALE_REG; + gfx_info.VI_Y_SCALE_REG = &VI_Y_SCALE_REG; + + gfx_info.CheckInterrupts = dummy_check_interrupts; + gfx_info.version = 2; + gfx_info.SP_STATUS_REG = &SP_STATUS_REG; + gfx_info.RDRAM_SIZE = &RDRAM_SIZE; + + InitiateGFX(gfx_info); +} + +void RT64SendDL(uint8_t* rdram, const OSTask* task) { + OSTask task_copy = *task; + task_copy.t.data_ptr &= 0x3FFFFFF; + task_copy.t.ucode &= 0x3FFFFFF; + task_copy.t.ucode_data &= 0x3FFFFFF; + + memcpy(DMEM + 0xFC0, &task_copy, 0x40); + + ProcessDList(); +} + +void RT64UpdateScreen(uint32_t vi_origin) { + VI_ORIGIN_REG = vi_origin; + + UpdateScreen(); +} diff --git a/src/sp.cpp b/src/sp.cpp new file mode 100644 index 0000000..b90daad --- /dev/null +++ b/src/sp.cpp @@ -0,0 +1,50 @@ +#include +#include +#include "../portultra/multilibultra.hpp" +#include "recomp.h" + +extern "C" void osSpTaskLoad_recomp(uint8_t* rdram, recomp_context* ctx) { + // Nothing to do here +} + +bool dump_frame = false; + +extern "C" void osSpTaskStartGo_recomp(uint8_t* rdram, recomp_context* ctx) { + //printf("[sp] osSpTaskStartGo(0x%08X)\n", (uint32_t)ctx->r4); + OSTask* task = TO_PTR(OSTask, ctx->r4); + if (task->t.type == M_GFXTASK) { + //printf("[sp] Gfx task: %08X\n", (uint32_t)ctx->r4); + } else if (task->t.type == M_AUDTASK) { + //printf("[sp] Audio task: %08X\n", (uint32_t)ctx->r4); + } + // For debugging + if (dump_frame) { + char addr_str[32]; + constexpr size_t ram_size = 0x800000; + std::unique_ptr ram_unswapped = std::make_unique(ram_size); + snprintf(addr_str, sizeof(addr_str) - 1, "%08X", task->t.data_ptr); + addr_str[sizeof(addr_str) - 1] = '\0'; + std::ofstream dump_file{ "ramdump" + std::string{ addr_str } + ".bin", std::ios::binary}; + + for (size_t i = 0; i < ram_size; i++) { + ram_unswapped[i] = rdram[i ^ 3]; + } + + dump_file.write(ram_unswapped.get(), ram_size); + dump_frame = false; + } + Multilibultra::submit_rsp_task(rdram, ctx->r4); +} + +extern "C" void osSpTaskYield_recomp(uint8_t* rdram, recomp_context* ctx) { + // Ignore yield requests (acts as if the task completed before it received the yield request) +} + +extern "C" void osSpTaskYielded_recomp(uint8_t* rdram, recomp_context* ctx) { + // Task yield requests are ignored, so always return 0 as tasks will never be yielded + ctx->r2 = 0; +} + +extern "C" void __osSpSetPc_recomp(uint8_t* rdram, recomp_context* ctx) { + assert(false); +} diff --git a/src/vi.cpp b/src/vi.cpp new file mode 100644 index 0000000..0b1f714 --- /dev/null +++ b/src/vi.cpp @@ -0,0 +1,38 @@ +#include "../portultra/multilibultra.hpp" +#include "recomp.h" + +extern "C" void osViSetYScale_recomp(uint8_t* rdram, recomp_context * ctx) { + ; +} + +extern "C" void osViSetXScale_recomp(uint8_t* rdram, recomp_context * ctx) { + ; +} + +extern "C" void osCreateViManager_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} + +extern "C" void osViBlack_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} + +extern "C" void osViSetSpecialFeatures_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} + +extern "C" void osViGetCurrentFramebuffer_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = (gpr)(int32_t)osViGetCurrentFramebuffer(); +} + +extern "C" void osViGetNextFramebuffer_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = (gpr)(int32_t)osViGetNextFramebuffer(); +} + +extern "C" void osViSwapBuffer_recomp(uint8_t* rdram, recomp_context* ctx) { + osViSwapBuffer(rdram, (int32_t)ctx->r4); +} + +extern "C" void osViSetMode_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} diff --git a/thirdparty/blockingconcurrentqueue.h b/thirdparty/blockingconcurrentqueue.h new file mode 100644 index 0000000..205a4db --- /dev/null +++ b/thirdparty/blockingconcurrentqueue.h @@ -0,0 +1,582 @@ +// Provides an efficient blocking version of moodycamel::ConcurrentQueue. +// ©2015-2020 Cameron Desrochers. Distributed under the terms of the simplified +// BSD license, available at the top of concurrentqueue.h. +// Also dual-licensed under the Boost Software License (see LICENSE.md) +// Uses Jeff Preshing's semaphore implementation (under the terms of its +// separate zlib license, see lightweightsemaphore.h). + +#pragma once + +#include "concurrentqueue.h" +#include "lightweightsemaphore.h" + +#include +#include +#include +#include +#include + +namespace moodycamel +{ +// This is a blocking version of the queue. It has an almost identical interface to +// the normal non-blocking version, with the addition of various wait_dequeue() methods +// and the removal of producer-specific dequeue methods. +template +class BlockingConcurrentQueue +{ +private: + typedef ::moodycamel::ConcurrentQueue ConcurrentQueue; + typedef ::moodycamel::LightweightSemaphore LightweightSemaphore; + +public: + typedef typename ConcurrentQueue::producer_token_t producer_token_t; + typedef typename ConcurrentQueue::consumer_token_t consumer_token_t; + + typedef typename ConcurrentQueue::index_t index_t; + typedef typename ConcurrentQueue::size_t size_t; + typedef typename std::make_signed::type ssize_t; + + static const size_t BLOCK_SIZE = ConcurrentQueue::BLOCK_SIZE; + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = ConcurrentQueue::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD; + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::EXPLICIT_INITIAL_INDEX_SIZE; + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::IMPLICIT_INITIAL_INDEX_SIZE; + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = ConcurrentQueue::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = ConcurrentQueue::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE; + static const size_t MAX_SUBQUEUE_SIZE = ConcurrentQueue::MAX_SUBQUEUE_SIZE; + +public: + // Creates a queue with at least `capacity` element slots; note that the + // actual number of elements that can be inserted without additional memory + // allocation depends on the number of producers and the block size (e.g. if + // the block size is equal to `capacity`, only a single block will be allocated + // up-front, which means only a single producer will be able to enqueue elements + // without an extra allocation -- blocks aren't shared between producers). + // This method is not thread safe -- it is up to the user to ensure that the + // queue is fully constructed before it starts being used by other threads (this + // includes making the memory effects of construction visible, possibly with a + // memory barrier). + explicit BlockingConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE) + : inner(capacity), sema(create(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy) + { + assert(reinterpret_cast((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member"); + if (!sema) { + MOODYCAMEL_THROW(std::bad_alloc()); + } + } + + BlockingConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) + : inner(minCapacity, maxExplicitProducers, maxImplicitProducers), sema(create(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy) + { + assert(reinterpret_cast((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member"); + if (!sema) { + MOODYCAMEL_THROW(std::bad_alloc()); + } + } + + // Disable copying and copy assignment + BlockingConcurrentQueue(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + BlockingConcurrentQueue& operator=(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + + // Moving is supported, but note that it is *not* a thread-safe operation. + // Nobody can use the queue while it's being moved, and the memory effects + // of that move must be propagated to other threads before they can use it. + // Note: When a queue is moved, its tokens are still valid but can only be + // used with the destination queue (i.e. semantically they are moved along + // with the queue itself). + BlockingConcurrentQueue(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + : inner(std::move(other.inner)), sema(std::move(other.sema)) + { } + + inline BlockingConcurrentQueue& operator=(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + { + return swap_internal(other); + } + + // Swaps this queue's state with the other's. Not thread-safe. + // Swapping two queues does not invalidate their tokens, however + // the tokens that were created for one queue must be used with + // only the swapped queue (i.e. the tokens are tied to the + // queue's movable state, not the object itself). + inline void swap(BlockingConcurrentQueue& other) MOODYCAMEL_NOEXCEPT + { + swap_internal(other); + } + +private: + BlockingConcurrentQueue& swap_internal(BlockingConcurrentQueue& other) + { + if (this == &other) { + return *this; + } + + inner.swap(other.inner); + sema.swap(other.sema); + return *this; + } + +public: + // Enqueues a single item (by copying it). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T const& item) + { + if ((details::likely)(inner.enqueue(item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T&& item) + { + if ((details::likely)(inner.enqueue(std::move(item)))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T const& item) + { + if ((details::likely)(inner.enqueue(token, item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T&& item) + { + if ((details::likely)(inner.enqueue(token, std::move(item)))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues several items. + // Allocates memory if required. Only fails if memory allocation fails (or + // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved instead of copied. + // Thread-safe. + template + inline bool enqueue_bulk(It itemFirst, size_t count) + { + if ((details::likely)(inner.enqueue_bulk(std::forward(itemFirst), count))) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + // Enqueues several items using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails + // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + inline bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + if ((details::likely)(inner.enqueue_bulk(token, std::forward(itemFirst), count))) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + // Enqueues a single item (by copying it). + // Does not allocate memory. Fails if not enough room to enqueue (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0). + // Thread-safe. + inline bool try_enqueue(T const& item) + { + if (inner.try_enqueue(item)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible). + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Thread-safe. + inline bool try_enqueue(T&& item) + { + if (inner.try_enqueue(std::move(item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T const& item) + { + if (inner.try_enqueue(token, item)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T&& item) + { + if (inner.try_enqueue(token, std::move(item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues several items. + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + inline bool try_enqueue_bulk(It itemFirst, size_t count) + { + if (inner.try_enqueue_bulk(std::forward(itemFirst), count)) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + // Enqueues several items using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + inline bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + if (inner.try_enqueue_bulk(token, std::forward(itemFirst), count)) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue(U& item) + { + if (sema->tryWait()) { + while (!inner.try_dequeue(item)) { + continue; + } + return true; + } + return false; + } + + // Attempts to dequeue from the queue using an explicit consumer token. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue(consumer_token_t& token, U& item) + { + if (sema->tryWait()) { + while (!inner.try_dequeue(token, item)) { + continue; + } + return true; + } + return false; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(token, itemFirst, max - count); + } + return count; + } + + + + // Blocks the current thread until there's something to dequeue, then + // dequeues it. + // Never allocates. Thread-safe. + template + inline void wait_dequeue(U& item) + { + while (!sema->wait()) { + continue; + } + while (!inner.try_dequeue(item)) { + continue; + } + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout (specified in microseconds) expires. Returns false + // without setting `item` if the timeout expires, otherwise assigns + // to `item` and returns true. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs) + { + if (!sema->wait(timeout_usecs)) { + return false; + } + while (!inner.try_dequeue(item)) { + continue; + } + return true; + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout expires. Returns false without setting `item` if the + // timeout expires, otherwise assigns to `item` and returns true. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(U& item, std::chrono::duration const& timeout) + { + return wait_dequeue_timed(item, std::chrono::duration_cast(timeout).count()); + } + + // Blocks the current thread until there's something to dequeue, then + // dequeues it using an explicit consumer token. + // Never allocates. Thread-safe. + template + inline void wait_dequeue(consumer_token_t& token, U& item) + { + while (!sema->wait()) { + continue; + } + while (!inner.try_dequeue(token, item)) { + continue; + } + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout (specified in microseconds) expires. Returns false + // without setting `item` if the timeout expires, otherwise assigns + // to `item` and returns true. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::int64_t timeout_usecs) + { + if (!sema->wait(timeout_usecs)) { + return false; + } + while (!inner.try_dequeue(token, item)) { + continue; + } + return true; + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout expires. Returns false without setting `item` if the + // timeout expires, otherwise assigns to `item` and returns true. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::chrono::duration const& timeout) + { + return wait_dequeue_timed(token, item, std::chrono::duration_cast(timeout).count()); + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued, which will + // always be at least one (this method blocks until the queue + // is non-empty) and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue_bulk. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::int64_t timeout_usecs) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs); + while (count != max) { + count += inner.template try_dequeue_bulk(itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::chrono::duration const& timeout) + { + return wait_dequeue_bulk_timed(itemFirst, max, std::chrono::duration_cast(timeout).count()); + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued, which will + // always be at least one (this method blocks until the queue + // is non-empty) and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(token, itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue_bulk. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::int64_t timeout_usecs) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs); + while (count != max) { + count += inner.template try_dequeue_bulk(token, itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::chrono::duration const& timeout) + { + return wait_dequeue_bulk_timed(token, itemFirst, max, std::chrono::duration_cast(timeout).count()); + } + + + // Returns an estimate of the total number of elements currently in the queue. This + // estimate is only accurate if the queue has completely stabilized before it is called + // (i.e. all enqueue and dequeue operations have completed and their memory effects are + // visible on the calling thread, and no further operations start while this method is + // being called). + // Thread-safe. + inline size_t size_approx() const + { + return (size_t)sema->availableApprox(); + } + + + // Returns true if the underlying atomic variables used by + // the queue are lock-free (they should be on most platforms). + // Thread-safe. + static constexpr bool is_lock_free() + { + return ConcurrentQueue::is_lock_free(); + } + + +private: + template + static inline U* create(A1&& a1, A2&& a2) + { + void* p = (Traits::malloc)(sizeof(U)); + return p != nullptr ? new (p) U(std::forward(a1), std::forward(a2)) : nullptr; + } + + template + static inline void destroy(U* p) + { + if (p != nullptr) { + p->~U(); + } + (Traits::free)(p); + } + +private: + ConcurrentQueue inner; + std::unique_ptr sema; +}; + + +template +inline void swap(BlockingConcurrentQueue& a, BlockingConcurrentQueue& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +} // end namespace moodycamel diff --git a/thirdparty/concurrentqueue.h b/thirdparty/concurrentqueue.h new file mode 100644 index 0000000..4b2ad79 --- /dev/null +++ b/thirdparty/concurrentqueue.h @@ -0,0 +1,3747 @@ +// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue. +// An overview, including benchmark results, is provided here: +// http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++ +// The full design is also described in excruciating detail at: +// http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue + +// Simplified BSD license: +// Copyright (c) 2013-2020, Cameron Desrochers. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Also dual-licensed under the Boost Software License (see LICENSE.md) + +#pragma once + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +// Disable -Wconversion warnings (spuriously triggered when Traits::size_t and +// Traits::index_t are set to < 32 bits, causing integer promotion, causing warnings +// upon assigning any computed values) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" + +#ifdef MCDBGQ_USE_RELACY +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" +#endif +#endif + +#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) +// VS2019 with /W4 warns about constant conditional expressions but unless /std=c++17 or higher +// does not support `if constexpr`, so we have no choice but to simply disable the warning +#pragma warning(push) +#pragma warning(disable: 4127) // conditional expression is constant +#endif + +#if defined(__APPLE__) +#include "TargetConditionals.h" +#endif + +#ifdef MCDBGQ_USE_RELACY +#include "relacy/relacy_std.hpp" +#include "relacy_shims.h" +// We only use malloc/free anyway, and the delete macro messes up `= delete` method declarations. +// We'll override the default trait malloc ourselves without a macro. +#undef new +#undef delete +#undef malloc +#undef free +#else +#include // Requires C++11. Sorry VS2010. +#include +#endif +#include // for max_align_t +#include +#include +#include +#include +#include +#include +#include // for CHAR_BIT +#include +#include // partly for __WINPTHREADS_VERSION if on MinGW-w64 w/ POSIX threading +#include // used for thread exit synchronization + +// Platform-specific definitions of a numeric thread ID type and an invalid value +namespace moodycamel { namespace details { + template struct thread_id_converter { + typedef thread_id_t thread_id_numeric_size_t; + typedef thread_id_t thread_id_hash_t; + static thread_id_hash_t prehash(thread_id_t const& x) { return x; } + }; +} } +#if defined(MCDBGQ_USE_RELACY) +namespace moodycamel { namespace details { + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0xFFFFFFFFU; + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFEU; + static inline thread_id_t thread_id() { return rl::thread_index(); } +} } +#elif defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__) +// No sense pulling in windows.h in a header, we'll manually declare the function +// we use and rely on backwards-compatibility for this not to break +extern "C" __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); +namespace moodycamel { namespace details { + static_assert(sizeof(unsigned long) == sizeof(std::uint32_t), "Expected size of unsigned long to be 32 bits on Windows"); + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // See http://blogs.msdn.com/b/oldnewthing/archive/2004/02/23/78395.aspx + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFFU; // Not technically guaranteed to be invalid, but is never used in practice. Note that all Win32 thread IDs are presently multiples of 4. + static inline thread_id_t thread_id() { return static_cast(::GetCurrentThreadId()); } +} } +#elif defined(__arm__) || defined(_M_ARM) || defined(__aarch64__) || (defined(__APPLE__) && TARGET_OS_IPHONE) || defined(MOODYCAMEL_NO_THREAD_LOCAL) +namespace moodycamel { namespace details { + static_assert(sizeof(std::thread::id) == 4 || sizeof(std::thread::id) == 8, "std::thread::id is expected to be either 4 or 8 bytes"); + + typedef std::thread::id thread_id_t; + static const thread_id_t invalid_thread_id; // Default ctor creates invalid ID + + // Note we don't define a invalid_thread_id2 since std::thread::id doesn't have one; it's + // only used if MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is defined anyway, which it won't + // be. + static inline thread_id_t thread_id() { return std::this_thread::get_id(); } + + template struct thread_id_size { }; + template<> struct thread_id_size<4> { typedef std::uint32_t numeric_t; }; + template<> struct thread_id_size<8> { typedef std::uint64_t numeric_t; }; + + template<> struct thread_id_converter { + typedef thread_id_size::numeric_t thread_id_numeric_size_t; +#ifndef __APPLE__ + typedef std::size_t thread_id_hash_t; +#else + typedef thread_id_numeric_size_t thread_id_hash_t; +#endif + + static thread_id_hash_t prehash(thread_id_t const& x) + { +#ifndef __APPLE__ + return std::hash()(x); +#else + return *reinterpret_cast(&x); +#endif + } + }; +} } +#else +// Use a nice trick from this answer: http://stackoverflow.com/a/8438730/21475 +// In order to get a numeric thread ID in a platform-independent way, we use a thread-local +// static variable's address as a thread identifier :-) +#if defined(__GNUC__) || defined(__INTEL_COMPILER) +#define MOODYCAMEL_THREADLOCAL __thread +#elif defined(_MSC_VER) +#define MOODYCAMEL_THREADLOCAL __declspec(thread) +#else +// Assume C++11 compliant compiler +#define MOODYCAMEL_THREADLOCAL thread_local +#endif +namespace moodycamel { namespace details { + typedef std::uintptr_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // Address can't be nullptr + static const thread_id_t invalid_thread_id2 = 1; // Member accesses off a null pointer are also generally invalid. Plus it's not aligned. + inline thread_id_t thread_id() { static MOODYCAMEL_THREADLOCAL int x; return reinterpret_cast(&x); } +} } +#endif + +// Constexpr if +#ifndef MOODYCAMEL_CONSTEXPR_IF +#if (defined(_MSC_VER) && defined(_HAS_CXX17) && _HAS_CXX17) || __cplusplus > 201402L +#define MOODYCAMEL_CONSTEXPR_IF if constexpr +#define MOODYCAMEL_MAYBE_UNUSED [[maybe_unused]] +#else +#define MOODYCAMEL_CONSTEXPR_IF if +#define MOODYCAMEL_MAYBE_UNUSED +#endif +#endif + +// Exceptions +#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED +#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__)) +#define MOODYCAMEL_EXCEPTIONS_ENABLED +#endif +#endif +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED +#define MOODYCAMEL_TRY try +#define MOODYCAMEL_CATCH(...) catch(__VA_ARGS__) +#define MOODYCAMEL_RETHROW throw +#define MOODYCAMEL_THROW(expr) throw (expr) +#else +#define MOODYCAMEL_TRY MOODYCAMEL_CONSTEXPR_IF (true) +#define MOODYCAMEL_CATCH(...) else MOODYCAMEL_CONSTEXPR_IF (false) +#define MOODYCAMEL_RETHROW +#define MOODYCAMEL_THROW(expr) +#endif + +#ifndef MOODYCAMEL_NOEXCEPT +#if !defined(MOODYCAMEL_EXCEPTIONS_ENABLED) +#define MOODYCAMEL_NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) true +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) true +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1800 +// VS2012's std::is_nothrow_[move_]constructible is broken and returns true when it shouldn't :-( +// We have to assume *all* non-trivial constructors may throw on VS2012! +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value : std::is_trivially_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1900 +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value || std::is_nothrow_move_constructible::value : std::is_trivially_copy_constructible::value || std::is_nothrow_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#else +#define MOODYCAMEL_NOEXCEPT noexcept +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) noexcept(expr) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) noexcept(expr) +#endif +#endif + +#ifndef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY +#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#else +// VS2013 doesn't support `thread_local`, and MinGW-w64 w/ POSIX threading has a crippling bug: http://sourceforge.net/p/mingw-w64/bugs/445 +// g++ <=4.7 doesn't support thread_local either. +// Finally, iOS/ARM doesn't have support for it either, and g++/ARM allows it to compile but it's unconfirmed to actually work +#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && (!defined(__MINGW32__) && !defined(__MINGW64__) || !defined(__WINPTHREADS_VERSION)) && (!defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) && (!defined(__APPLE__) || !TARGET_OS_IPHONE) && !defined(__arm__) && !defined(_M_ARM) && !defined(__aarch64__) +// Assume `thread_local` is fully supported in all other C++11 compilers/platforms +#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED // tentatively enabled for now; years ago several users report having problems with it on +#endif +#endif +#endif + +// VS2012 doesn't support deleted functions. +// In this case, we declare the function normally but don't define it. A link error will be generated if the function is called. +#ifndef MOODYCAMEL_DELETE_FUNCTION +#if defined(_MSC_VER) && _MSC_VER < 1800 +#define MOODYCAMEL_DELETE_FUNCTION +#else +#define MOODYCAMEL_DELETE_FUNCTION = delete +#endif +#endif + +namespace moodycamel { namespace details { +#ifndef MOODYCAMEL_ALIGNAS +// VS2013 doesn't support alignas or alignof, and align() requires a constant literal +#if defined(_MSC_VER) && _MSC_VER <= 1800 +#define MOODYCAMEL_ALIGNAS(alignment) __declspec(align(alignment)) +#define MOODYCAMEL_ALIGNOF(obj) __alignof(obj) +#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) typename details::Vs2013Aligned::value, T>::type + template struct Vs2013Aligned { }; // default, unsupported alignment + template struct Vs2013Aligned<1, T> { typedef __declspec(align(1)) T type; }; + template struct Vs2013Aligned<2, T> { typedef __declspec(align(2)) T type; }; + template struct Vs2013Aligned<4, T> { typedef __declspec(align(4)) T type; }; + template struct Vs2013Aligned<8, T> { typedef __declspec(align(8)) T type; }; + template struct Vs2013Aligned<16, T> { typedef __declspec(align(16)) T type; }; + template struct Vs2013Aligned<32, T> { typedef __declspec(align(32)) T type; }; + template struct Vs2013Aligned<64, T> { typedef __declspec(align(64)) T type; }; + template struct Vs2013Aligned<128, T> { typedef __declspec(align(128)) T type; }; + template struct Vs2013Aligned<256, T> { typedef __declspec(align(256)) T type; }; +#else + template struct identity { typedef T type; }; +#define MOODYCAMEL_ALIGNAS(alignment) alignas(alignment) +#define MOODYCAMEL_ALIGNOF(obj) alignof(obj) +#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) alignas(alignof(obj)) typename details::identity::type +#endif +#endif +} } + + +// TSAN can false report races in lock-free code. To enable TSAN to be used from projects that use this one, +// we can apply per-function compile-time suppression. +// See https://clang.llvm.org/docs/ThreadSanitizer.html#has-feature-thread-sanitizer +#define MOODYCAMEL_NO_TSAN +#if defined(__has_feature) + #if __has_feature(thread_sanitizer) + #undef MOODYCAMEL_NO_TSAN + #define MOODYCAMEL_NO_TSAN __attribute__((no_sanitize("thread"))) + #endif // TSAN +#endif // TSAN + +// Compiler-specific likely/unlikely hints +namespace moodycamel { namespace details { +#if defined(__GNUC__) + static inline bool (likely)(bool x) { return __builtin_expect((x), true); } + static inline bool (unlikely)(bool x) { return __builtin_expect((x), false); } +#else + static inline bool (likely)(bool x) { return x; } + static inline bool (unlikely)(bool x) { return x; } +#endif +} } + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG +#include "internal/concurrentqueue_internal_debug.h" +#endif + +namespace moodycamel { +namespace details { + template + struct const_numeric_max { + static_assert(std::is_integral::value, "const_numeric_max can only be used with integers"); + static const T value = std::numeric_limits::is_signed + ? (static_cast(1) << (sizeof(T) * CHAR_BIT - 1)) - static_cast(1) + : static_cast(-1); + }; + +#if defined(__GLIBCXX__) + typedef ::max_align_t std_max_align_t; // libstdc++ forgot to add it to std:: for a while +#else + typedef std::max_align_t std_max_align_t; // Others (e.g. MSVC) insist it can *only* be accessed via std:: +#endif + + // Some platforms have incorrectly set max_align_t to a type with <8 bytes alignment even while supporting + // 8-byte aligned scalar values (*cough* 32-bit iOS). Work around this with our own union. See issue #64. + typedef union { + std_max_align_t x; + long long y; + void* z; + } max_align_t; +} + +// Default traits for the ConcurrentQueue. To change some of the +// traits without re-implementing all of them, inherit from this +// struct and shadow the declarations you wish to be different; +// since the traits are used as a template type parameter, the +// shadowed declarations will be used where defined, and the defaults +// otherwise. +struct ConcurrentQueueDefaultTraits +{ + // General-purpose size type. std::size_t is strongly recommended. + typedef std::size_t size_t; + + // The type used for the enqueue and dequeue indices. Must be at least as + // large as size_t. Should be significantly larger than the number of elements + // you expect to hold at once, especially if you have a high turnover rate; + // for example, on 32-bit x86, if you expect to have over a hundred million + // elements or pump several million elements through your queue in a very + // short space of time, using a 32-bit type *may* trigger a race condition. + // A 64-bit int type is recommended in that case, and in practice will + // prevent a race condition no matter the usage of the queue. Note that + // whether the queue is lock-free with a 64-int type depends on the whether + // std::atomic is lock-free, which is platform-specific. + typedef std::size_t index_t; + + // Internally, all elements are enqueued and dequeued from multi-element + // blocks; this is the smallest controllable unit. If you expect few elements + // but many producers, a smaller block size should be favoured. For few producers + // and/or many elements, a larger block size is preferred. A sane default + // is provided. Must be a power of 2. + static const size_t BLOCK_SIZE = 32; + + // For explicit producers (i.e. when using a producer token), the block is + // checked for being empty by iterating through a list of flags, one per element. + // For large block sizes, this is too inefficient, and switching to an atomic + // counter-based approach is faster. The switch is made for block sizes strictly + // larger than this threshold. + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = 32; + + // How many full blocks can be expected for a single explicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 32; + + // How many full blocks can be expected for a single implicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 32; + + // The initial size of the hash table mapping thread IDs to implicit producers. + // Note that the hash is resized every time it becomes half full. + // Must be a power of two, and either 0 or at least 1. If 0, implicit production + // (using the enqueue methods without an explicit producer token) is disabled. + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = 32; + + // Controls the number of items that an explicit consumer (i.e. one with a token) + // must consume before it causes all consumers to rotate and move on to the next + // internal queue. + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 256; + + // The maximum number of elements (inclusive) that can be enqueued to a sub-queue. + // Enqueue operations that would cause this limit to be surpassed will fail. Note + // that this limit is enforced at the block level (for performance reasons), i.e. + // it's rounded up to the nearest block size. + static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max::value; + + // The number of times to spin before sleeping when waiting on a semaphore. + // Recommended values are on the order of 1000-10000 unless the number of + // consumer threads exceeds the number of idle cores (in which case try 0-100). + // Only affects instances of the BlockingConcurrentQueue. + static const int MAX_SEMA_SPINS = 10000; + + // Whether to recycle dynamically-allocated blocks into an internal free list or + // not. If false, only pre-allocated blocks (controlled by the constructor + // arguments) will be recycled, and all others will be `free`d back to the heap. + // Note that blocks consumed by explicit producers are only freed on destruction + // of the queue (not following destruction of the token) regardless of this trait. + static const bool RECYCLE_ALLOCATED_BLOCKS = false; + + +#ifndef MCDBGQ_USE_RELACY + // Memory allocation can be customized if needed. + // malloc should return nullptr on failure, and handle alignment like std::malloc. +#if defined(malloc) || defined(free) + // Gah, this is 2015, stop defining macros that break standard code already! + // Work around malloc/free being special macros: + static inline void* WORKAROUND_malloc(size_t size) { return malloc(size); } + static inline void WORKAROUND_free(void* ptr) { return free(ptr); } + static inline void* (malloc)(size_t size) { return WORKAROUND_malloc(size); } + static inline void (free)(void* ptr) { return WORKAROUND_free(ptr); } +#else + static inline void* malloc(size_t size) { return std::malloc(size); } + static inline void free(void* ptr) { return std::free(ptr); } +#endif +#else + // Debug versions when running under the Relacy race detector (ignore + // these in user code) + static inline void* malloc(size_t size) { return rl::rl_malloc(size, $); } + static inline void free(void* ptr) { return rl::rl_free(ptr, $); } +#endif +}; + + +// When producing or consuming many elements, the most efficient way is to: +// 1) Use one of the bulk-operation methods of the queue with a token +// 2) Failing that, use the bulk-operation methods without a token +// 3) Failing that, create a token and use that with the single-item methods +// 4) Failing that, use the single-parameter methods of the queue +// Having said that, don't create tokens willy-nilly -- ideally there should be +// a maximum of one token per thread (of each kind). +struct ProducerToken; +struct ConsumerToken; + +template class ConcurrentQueue; +template class BlockingConcurrentQueue; +class ConcurrentQueueTests; + + +namespace details +{ + struct ConcurrentQueueProducerTypelessBase + { + ConcurrentQueueProducerTypelessBase* next; + std::atomic inactive; + ProducerToken* token; + + ConcurrentQueueProducerTypelessBase() + : next(nullptr), inactive(false), token(nullptr) + { + } + }; + + template struct _hash_32_or_64 { + static inline std::uint32_t hash(std::uint32_t h) + { + // MurmurHash3 finalizer -- see https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp + // Since the thread ID is already unique, all we really want to do is propagate that + // uniqueness evenly across all the bits, so that we can use a subset of the bits while + // reducing collisions significantly + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + return h ^ (h >> 16); + } + }; + template<> struct _hash_32_or_64<1> { + static inline std::uint64_t hash(std::uint64_t h) + { + h ^= h >> 33; + h *= 0xff51afd7ed558ccd; + h ^= h >> 33; + h *= 0xc4ceb9fe1a85ec53; + return h ^ (h >> 33); + } + }; + template struct hash_32_or_64 : public _hash_32_or_64<(size > 4)> { }; + + static inline size_t hash_thread_id(thread_id_t id) + { + static_assert(sizeof(thread_id_t) <= 8, "Expected a platform where thread IDs are at most 64-bit values"); + return static_cast(hash_32_or_64::thread_id_hash_t)>::hash( + thread_id_converter::prehash(id))); + } + + template + static inline bool circular_less_than(T a, T b) + { + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "circular_less_than is intended to be used only with unsigned integer types"); + return static_cast(a - b) > static_cast(static_cast(1) << (static_cast(sizeof(T) * CHAR_BIT - 1))); + // Note: extra parens around rhs of operator<< is MSVC bug: https://developercommunity2.visualstudio.com/t/C4554-triggers-when-both-lhs-and-rhs-is/10034931 + // silencing the bug requires #pragma warning(disable: 4554) around the calling code and has no effect when done here. + } + + template + static inline char* align_for(char* ptr) + { + const std::size_t alignment = std::alignment_of::value; + return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; + } + + template + static inline T ceil_to_pow_2(T x) + { + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "ceil_to_pow_2 is intended to be used only with unsigned integer types"); + + // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + for (std::size_t i = 1; i < sizeof(T); i <<= 1) { + x |= x >> (i << 3); + } + ++x; + return x; + } + + template + static inline void swap_relaxed(std::atomic& left, std::atomic& right) + { + T temp = std::move(left.load(std::memory_order_relaxed)); + left.store(std::move(right.load(std::memory_order_relaxed)), std::memory_order_relaxed); + right.store(std::move(temp), std::memory_order_relaxed); + } + + template + static inline T const& nomove(T const& x) + { + return x; + } + + template + struct nomove_if + { + template + static inline T const& eval(T const& x) + { + return x; + } + }; + + template<> + struct nomove_if + { + template + static inline auto eval(U&& x) + -> decltype(std::forward(x)) + { + return std::forward(x); + } + }; + + template + static inline auto deref_noexcept(It& it) MOODYCAMEL_NOEXCEPT -> decltype(*it) + { + return *it; + } + +#if defined(__clang__) || !defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + template struct is_trivially_destructible : std::is_trivially_destructible { }; +#else + template struct is_trivially_destructible : std::has_trivial_destructor { }; +#endif + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY + typedef RelacyThreadExitListener ThreadExitListener; + typedef RelacyThreadExitNotifier ThreadExitNotifier; +#else + class ThreadExitNotifier; + + struct ThreadExitListener + { + typedef void (*callback_t)(void*); + callback_t callback; + void* userData; + + ThreadExitListener* next; // reserved for use by the ThreadExitNotifier + ThreadExitNotifier* chain; // reserved for use by the ThreadExitNotifier + }; + + class ThreadExitNotifier + { + public: + static void subscribe(ThreadExitListener* listener) + { + auto& tlsInst = instance(); + std::lock_guard guard(mutex()); + listener->next = tlsInst.tail; + listener->chain = &tlsInst; + tlsInst.tail = listener; + } + + static void unsubscribe(ThreadExitListener* listener) + { + std::lock_guard guard(mutex()); + if (!listener->chain) { + return; // race with ~ThreadExitNotifier + } + auto& tlsInst = *listener->chain; + listener->chain = nullptr; + ThreadExitListener** prev = &tlsInst.tail; + for (auto ptr = tlsInst.tail; ptr != nullptr; ptr = ptr->next) { + if (ptr == listener) { + *prev = ptr->next; + break; + } + prev = &ptr->next; + } + } + + private: + ThreadExitNotifier() : tail(nullptr) { } + ThreadExitNotifier(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + ThreadExitNotifier& operator=(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + + ~ThreadExitNotifier() + { + // This thread is about to exit, let everyone know! + assert(this == &instance() && "If this assert fails, you likely have a buggy compiler! Change the preprocessor conditions such that MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is no longer defined."); + std::lock_guard guard(mutex()); + for (auto ptr = tail; ptr != nullptr; ptr = ptr->next) { + ptr->chain = nullptr; + ptr->callback(ptr->userData); + } + } + + // Thread-local + static inline ThreadExitNotifier& instance() + { + static thread_local ThreadExitNotifier notifier; + return notifier; + } + + static inline std::mutex& mutex() + { + // Must be static because the ThreadExitNotifier could be destroyed while unsubscribe is called + static std::mutex mutex; + return mutex; + } + + private: + ThreadExitListener* tail; + }; +#endif +#endif + + template struct static_is_lock_free_num { enum { value = 0 }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_CHAR_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_SHORT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_INT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LONG_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LLONG_LOCK_FREE }; }; + template struct static_is_lock_free : static_is_lock_free_num::type> { }; + template<> struct static_is_lock_free { enum { value = ATOMIC_BOOL_LOCK_FREE }; }; + template struct static_is_lock_free { enum { value = ATOMIC_POINTER_LOCK_FREE }; }; +} + + +struct ProducerToken +{ + template + explicit ProducerToken(ConcurrentQueue& queue); + + template + explicit ProducerToken(BlockingConcurrentQueue& queue); + + ProducerToken(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + : producer(other.producer) + { + other.producer = nullptr; + if (producer != nullptr) { + producer->token = this; + } + } + + inline ProducerToken& operator=(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ProducerToken& other) MOODYCAMEL_NOEXCEPT + { + std::swap(producer, other.producer); + if (producer != nullptr) { + producer->token = this; + } + if (other.producer != nullptr) { + other.producer->token = &other; + } + } + + // A token is always valid unless: + // 1) Memory allocation failed during construction + // 2) It was moved via the move constructor + // (Note: assignment does a swap, leaving both potentially valid) + // 3) The associated queue was destroyed + // Note that if valid() returns true, that only indicates + // that the token is valid for use with a specific queue, + // but not which one; that's up to the user to track. + inline bool valid() const { return producer != nullptr; } + + ~ProducerToken() + { + if (producer != nullptr) { + producer->token = nullptr; + producer->inactive.store(true, std::memory_order_release); + } + } + + // Disable copying and assignment + ProducerToken(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ProducerToken& operator=(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + +private: + template friend class ConcurrentQueue; + friend class ConcurrentQueueTests; + +protected: + details::ConcurrentQueueProducerTypelessBase* producer; +}; + + +struct ConsumerToken +{ + template + explicit ConsumerToken(ConcurrentQueue& q); + + template + explicit ConsumerToken(BlockingConcurrentQueue& q); + + ConsumerToken(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + : initialOffset(other.initialOffset), lastKnownGlobalOffset(other.lastKnownGlobalOffset), itemsConsumedFromCurrent(other.itemsConsumedFromCurrent), currentProducer(other.currentProducer), desiredProducer(other.desiredProducer) + { + } + + inline ConsumerToken& operator=(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ConsumerToken& other) MOODYCAMEL_NOEXCEPT + { + std::swap(initialOffset, other.initialOffset); + std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset); + std::swap(itemsConsumedFromCurrent, other.itemsConsumedFromCurrent); + std::swap(currentProducer, other.currentProducer); + std::swap(desiredProducer, other.desiredProducer); + } + + // Disable copying and assignment + ConsumerToken(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ConsumerToken& operator=(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + +private: + template friend class ConcurrentQueue; + friend class ConcurrentQueueTests; + +private: // but shared with ConcurrentQueue + std::uint32_t initialOffset; + std::uint32_t lastKnownGlobalOffset; + std::uint32_t itemsConsumedFromCurrent; + details::ConcurrentQueueProducerTypelessBase* currentProducer; + details::ConcurrentQueueProducerTypelessBase* desiredProducer; +}; + +// Need to forward-declare this swap because it's in a namespace. +// See http://stackoverflow.com/questions/4492062/why-does-a-c-friend-class-need-a-forward-declaration-only-in-other-namespaces +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT; + + +template +class ConcurrentQueue +{ +public: + typedef ::moodycamel::ProducerToken producer_token_t; + typedef ::moodycamel::ConsumerToken consumer_token_t; + + typedef typename Traits::index_t index_t; + typedef typename Traits::size_t size_t; + + static const size_t BLOCK_SIZE = static_cast(Traits::BLOCK_SIZE); + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = static_cast(Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD); + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::EXPLICIT_INITIAL_INDEX_SIZE); + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::IMPLICIT_INITIAL_INDEX_SIZE); + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = static_cast(Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE); + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = static_cast(Traits::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE); +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4307) // + integral constant overflow (that's what the ternary expression is for!) +#pragma warning(disable: 4309) // static_cast: Truncation of constant value +#endif + static const size_t MAX_SUBQUEUE_SIZE = (details::const_numeric_max::value - static_cast(Traits::MAX_SUBQUEUE_SIZE) < BLOCK_SIZE) ? details::const_numeric_max::value : ((static_cast(Traits::MAX_SUBQUEUE_SIZE) + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::size_t must be an unsigned integral type"); + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::index_t must be an unsigned integral type"); + static_assert(sizeof(index_t) >= sizeof(size_t), "Traits::index_t must be at least as wide as Traits::size_t"); + static_assert((BLOCK_SIZE > 1) && !(BLOCK_SIZE & (BLOCK_SIZE - 1)), "Traits::BLOCK_SIZE must be a power of 2 (and at least 2)"); + static_assert((EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD > 1) && !(EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD & (EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD - 1)), "Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD must be a power of 2 (and greater than 1)"); + static_assert((EXPLICIT_INITIAL_INDEX_SIZE > 1) && !(EXPLICIT_INITIAL_INDEX_SIZE & (EXPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::EXPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((IMPLICIT_INITIAL_INDEX_SIZE > 1) && !(IMPLICIT_INITIAL_INDEX_SIZE & (IMPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::IMPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) || !(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE & (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - 1)), "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be a power of 2"); + static_assert(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0 || INITIAL_IMPLICIT_PRODUCER_HASH_SIZE >= 1, "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be at least 1 (or 0 to disable implicit enqueueing)"); + +public: + // Creates a queue with at least `capacity` element slots; note that the + // actual number of elements that can be inserted without additional memory + // allocation depends on the number of producers and the block size (e.g. if + // the block size is equal to `capacity`, only a single block will be allocated + // up-front, which means only a single producer will be able to enqueue elements + // without an extra allocation -- blocks aren't shared between producers). + // This method is not thread safe -- it is up to the user to ensure that the + // queue is fully constructed before it starts being used by other threads (this + // includes making the memory effects of construction visible, possibly with a + // memory barrier). + explicit ConcurrentQueue(size_t capacity = 32 * BLOCK_SIZE) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + // Track all the producers using a fully-resolved typed list for + // each kind; this makes it possible to debug them starting from + // the root queue object (otherwise wacky casts are needed that + // don't compile in the debugger's expression evaluator). + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Computes the correct amount of pre-allocated blocks for you based + // on the minimum number of elements you want available at any given + // time, and the maximum concurrent number of each type of producer. + ConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + size_t blocks = (((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + 2 * (maxExplicitProducers + maxImplicitProducers); + populate_initial_block_list(blocks); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Note: The queue should not be accessed concurrently while it's + // being deleted. It's up to the user to synchronize this. + // This method is not thread safe. + ~ConcurrentQueue() + { + // Destroy producers + auto ptr = producerListTail.load(std::memory_order_relaxed); + while (ptr != nullptr) { + auto next = ptr->next_prod(); + if (ptr->token != nullptr) { + ptr->token->producer = nullptr; + } + destroy(ptr); + ptr = next; + } + + // Destroy implicit producer hash tables + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE != 0) { + auto hash = implicitProducerHash.load(std::memory_order_relaxed); + while (hash != nullptr) { + auto prev = hash->prev; + if (prev != nullptr) { // The last hash is part of this object and was not allocated dynamically + for (size_t i = 0; i != hash->capacity; ++i) { + hash->entries[i].~ImplicitProducerKVP(); + } + hash->~ImplicitProducerHash(); + (Traits::free)(hash); + } + hash = prev; + } + } + + // Destroy global free list + auto block = freeList.head_unsafe(); + while (block != nullptr) { + auto next = block->freeListNext.load(std::memory_order_relaxed); + if (block->dynamicallyAllocated) { + destroy(block); + } + block = next; + } + + // Destroy initial free list + destroy_array(initialBlockPool, initialBlockPoolSize); + } + + // Disable copying and copy assignment + ConcurrentQueue(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + ConcurrentQueue& operator=(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + + // Moving is supported, but note that it is *not* a thread-safe operation. + // Nobody can use the queue while it's being moved, and the memory effects + // of that move must be propagated to other threads before they can use it. + // Note: When a queue is moved, its tokens are still valid but can only be + // used with the destination queue (i.e. semantically they are moved along + // with the queue itself). + ConcurrentQueue(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + : producerListTail(other.producerListTail.load(std::memory_order_relaxed)), + producerCount(other.producerCount.load(std::memory_order_relaxed)), + initialBlockPoolIndex(other.initialBlockPoolIndex.load(std::memory_order_relaxed)), + initialBlockPool(other.initialBlockPool), + initialBlockPoolSize(other.initialBlockPoolSize), + freeList(std::move(other.freeList)), + nextExplicitConsumerId(other.nextExplicitConsumerId.load(std::memory_order_relaxed)), + globalExplicitConsumerOffset(other.globalExplicitConsumerOffset.load(std::memory_order_relaxed)) + { + // Move the other one into this, and leave the other one as an empty queue + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + swap_implicit_producer_hashes(other); + + other.producerListTail.store(nullptr, std::memory_order_relaxed); + other.producerCount.store(0, std::memory_order_relaxed); + other.nextExplicitConsumerId.store(0, std::memory_order_relaxed); + other.globalExplicitConsumerOffset.store(0, std::memory_order_relaxed); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(other.explicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(other.implicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + + other.initialBlockPoolIndex.store(0, std::memory_order_relaxed); + other.initialBlockPoolSize = 0; + other.initialBlockPool = nullptr; + + reown_producers(); + } + + inline ConcurrentQueue& operator=(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + { + return swap_internal(other); + } + + // Swaps this queue's state with the other's. Not thread-safe. + // Swapping two queues does not invalidate their tokens, however + // the tokens that were created for one queue must be used with + // only the swapped queue (i.e. the tokens are tied to the + // queue's movable state, not the object itself). + inline void swap(ConcurrentQueue& other) MOODYCAMEL_NOEXCEPT + { + swap_internal(other); + } + +private: + ConcurrentQueue& swap_internal(ConcurrentQueue& other) + { + if (this == &other) { + return *this; + } + + details::swap_relaxed(producerListTail, other.producerListTail); + details::swap_relaxed(producerCount, other.producerCount); + details::swap_relaxed(initialBlockPoolIndex, other.initialBlockPoolIndex); + std::swap(initialBlockPool, other.initialBlockPool); + std::swap(initialBlockPoolSize, other.initialBlockPoolSize); + freeList.swap(other.freeList); + details::swap_relaxed(nextExplicitConsumerId, other.nextExplicitConsumerId); + details::swap_relaxed(globalExplicitConsumerOffset, other.globalExplicitConsumerOffset); + + swap_implicit_producer_hashes(other); + + reown_producers(); + other.reown_producers(); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + details::swap_relaxed(explicitProducers, other.explicitProducers); + details::swap_relaxed(implicitProducers, other.implicitProducers); +#endif + + return *this; + } + +public: + // Enqueues a single item (by copying it). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T const& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T&& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T const& item) + { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T&& item) + { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Allocates memory if required. Only fails if memory allocation fails (or + // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved instead of copied. + // Thread-safe. + template + bool enqueue_bulk(It itemFirst, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue_bulk(itemFirst, count); + } + + // Enqueues several items using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails + // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return inner_enqueue_bulk(token, itemFirst, count); + } + + // Enqueues a single item (by copying it). + // Does not allocate memory. Fails if not enough room to enqueue (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0). + // Thread-safe. + inline bool try_enqueue(T const& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Thread-safe. + inline bool try_enqueue(T&& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T const& item) + { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T&& item) + { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(It itemFirst, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue_bulk(itemFirst, count); + } + + // Enqueues several items using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return inner_enqueue_bulk(token, itemFirst, count); + } + + + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(U& item) + { + // Instead of simply trying each producer in turn (which could cause needless contention on the first + // producer), we score them heuristically. + size_t nonEmptyCount = 0; + ProducerBase* best = nullptr; + size_t bestSize = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); nonEmptyCount < 3 && ptr != nullptr; ptr = ptr->next_prod()) { + auto size = ptr->size_approx(); + if (size > 0) { + if (size > bestSize) { + bestSize = size; + best = ptr; + } + ++nonEmptyCount; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (nonEmptyCount > 0) { + if ((details::likely)(best->dequeue(item))) { + return true; + } + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr != best && ptr->dequeue(item)) { + return true; + } + } + } + return false; + } + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // This differs from the try_dequeue(item) method in that this one does + // not attempt to reduce contention by interleaving the order that producer + // streams are dequeued from. So, using this method can reduce overall throughput + // under contention, but will give more predictable results in single-threaded + // consumer scenarios. This is mostly only useful for internal unit tests. + // Never allocates. Thread-safe. + template + bool try_dequeue_non_interleaved(U& item) + { + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->dequeue(item)) { + return true; + } + } + return false; + } + + // Attempts to dequeue from the queue using an explicit consumer token. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(consumer_token_t& token, U& item) + { + // The idea is roughly as follows: + // Every 256 items from one producer, make everyone rotate (increase the global offset) -> this means the highest efficiency consumer dictates the rotation speed of everyone else, more or less + // If you see that the global offset has changed, you must reset your consumption counter and move to your designated place + // If there's no items where you're supposed to be, keep moving until you find a producer with some items + // If the global offset has not changed but you've run out of items to consume, move over from your current position until you find an producer with something in it + + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return false; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (static_cast(token.currentProducer)->dequeue(item)) { + if (++token.itemsConsumedFromCurrent == EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return true; + } + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + if (ptr->dequeue(item)) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = 1; + return true; + } + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return false; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + count += ptr->dequeue_bulk(itemFirst, max - count); + if (count == max) { + break; + } + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return 0; + } + } + + size_t count = static_cast(token.currentProducer)->dequeue_bulk(itemFirst, max); + if (count == max) { + if ((token.itemsConsumedFromCurrent += static_cast(max)) >= EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return max; + } + token.itemsConsumedFromCurrent += static_cast(count); + max -= count; + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + auto dequeued = ptr->dequeue_bulk(itemFirst, max); + count += dequeued; + if (dequeued != 0) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = static_cast(dequeued); + } + if (dequeued == max) { + break; + } + max -= dequeued; + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return count; + } + + + + // Attempts to dequeue from a specific producer's inner queue. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns false if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue_from_producer(producer_token_t const& producer, U& item) + { + return static_cast(producer.producer)->dequeue(item); + } + + // Attempts to dequeue several elements from a specific producer's inner queue. + // Returns the number of items actually dequeued. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns 0 if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk_from_producer(producer_token_t const& producer, It itemFirst, size_t max) + { + return static_cast(producer.producer)->dequeue_bulk(itemFirst, max); + } + + + // Returns an estimate of the total number of elements currently in the queue. This + // estimate is only accurate if the queue has completely stabilized before it is called + // (i.e. all enqueue and dequeue operations have completed and their memory effects are + // visible on the calling thread, and no further operations start while this method is + // being called). + // Thread-safe. + size_t size_approx() const + { + size_t size = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + size += ptr->size_approx(); + } + return size; + } + + + // Returns true if the underlying atomic variables used by + // the queue are lock-free (they should be on most platforms). + // Thread-safe. + static constexpr bool is_lock_free() + { + return + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::thread_id_numeric_size_t>::value == 2; + } + + +private: + friend struct ProducerToken; + friend struct ConsumerToken; + struct ExplicitProducer; + friend struct ExplicitProducer; + struct ImplicitProducer; + friend struct ImplicitProducer; + friend class ConcurrentQueueTests; + + enum AllocationMode { CanAlloc, CannotAlloc }; + + + /////////////////////////////// + // Queue methods + /////////////////////////////// + + template + inline bool inner_enqueue(producer_token_t const& token, U&& element) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue(std::forward(element)); + } + + template + inline bool inner_enqueue(U&& element) + { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue(std::forward(element)); + } + + template + inline bool inner_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue_bulk(itemFirst, count); + } + + template + inline bool inner_enqueue_bulk(It itemFirst, size_t count) + { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue_bulk(itemFirst, count); + } + + inline bool update_current_producer_after_rotation(consumer_token_t& token) + { + // Ah, there's been a rotation, figure out where we should be! + auto tail = producerListTail.load(std::memory_order_acquire); + if (token.desiredProducer == nullptr && tail == nullptr) { + return false; + } + auto prodCount = producerCount.load(std::memory_order_relaxed); + auto globalOffset = globalExplicitConsumerOffset.load(std::memory_order_relaxed); + if ((details::unlikely)(token.desiredProducer == nullptr)) { + // Aha, first time we're dequeueing anything. + // Figure out our local position + // Note: offset is from start, not end, but we're traversing from end -- subtract from count first + std::uint32_t offset = prodCount - 1 - (token.initialOffset % prodCount); + token.desiredProducer = tail; + for (std::uint32_t i = 0; i != offset; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + } + + std::uint32_t delta = globalOffset - token.lastKnownGlobalOffset; + if (delta >= prodCount) { + delta = delta % prodCount; + } + for (std::uint32_t i = 0; i != delta; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + + token.lastKnownGlobalOffset = globalOffset; + token.currentProducer = token.desiredProducer; + token.itemsConsumedFromCurrent = 0; + return true; + } + + + /////////////////////////// + // Free list + /////////////////////////// + + template + struct FreeListNode + { + FreeListNode() : freeListRefs(0), freeListNext(nullptr) { } + + std::atomic freeListRefs; + std::atomic freeListNext; + }; + + // A simple CAS-based lock-free free list. Not the fastest thing in the world under heavy contention, but + // simple and correct (assuming nodes are never freed until after the free list is destroyed), and fairly + // speedy under low contention. + template // N must inherit FreeListNode or have the same fields (and initialization of them) + struct FreeList + { + FreeList() : freeListHead(nullptr) { } + FreeList(FreeList&& other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { other.freeListHead.store(nullptr, std::memory_order_relaxed); } + void swap(FreeList& other) { details::swap_relaxed(freeListHead, other.freeListHead); } + + FreeList(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + FreeList& operator=(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + + inline void add(N* node) + { +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + // We know that the should-be-on-freelist bit is 0 at this point, so it's safe to + // set it using a fetch_add + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) { + // Oh look! We were the last ones referencing this node, and we know + // we want to add it to the free list, so let's do it! + add_knowing_refcount_is_zero(node); + } + } + + inline N* try_get() + { +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + auto head = freeListHead.load(std::memory_order_acquire); + while (head != nullptr) { + auto prevHead = head; + auto refs = head->freeListRefs.load(std::memory_order_relaxed); + if ((refs & REFS_MASK) == 0 || !head->freeListRefs.compare_exchange_strong(refs, refs + 1, std::memory_order_acquire, std::memory_order_relaxed)) { + head = freeListHead.load(std::memory_order_acquire); + continue; + } + + // Good, reference count has been incremented (it wasn't at zero), which means we can read the + // next and not worry about it changing between now and the time we do the CAS + auto next = head->freeListNext.load(std::memory_order_relaxed); + if (freeListHead.compare_exchange_strong(head, next, std::memory_order_acquire, std::memory_order_relaxed)) { + // Yay, got the node. This means it was on the list, which means shouldBeOnFreeList must be false no + // matter the refcount (because nobody else knows it's been taken off yet, it can't have been put back on). + assert((head->freeListRefs.load(std::memory_order_relaxed) & SHOULD_BE_ON_FREELIST) == 0); + + // Decrease refcount twice, once for our ref, and once for the list's ref + head->freeListRefs.fetch_sub(2, std::memory_order_release); + return head; + } + + // OK, the head must have changed on us, but we still need to decrease the refcount we increased. + // Note that we don't need to release any memory effects, but we do need to ensure that the reference + // count decrement happens-after the CAS on the head. + refs = prevHead->freeListRefs.fetch_sub(1, std::memory_order_acq_rel); + if (refs == SHOULD_BE_ON_FREELIST + 1) { + add_knowing_refcount_is_zero(prevHead); + } + } + + return nullptr; + } + + // Useful for traversing the list when there's no contention (e.g. to destroy remaining nodes) + N* head_unsafe() const { return freeListHead.load(std::memory_order_relaxed); } + + private: + inline void add_knowing_refcount_is_zero(N* node) + { + // Since the refcount is zero, and nobody can increase it once it's zero (except us, and we run + // only one copy of this method per node at a time, i.e. the single thread case), then we know + // we can safely change the next pointer of the node; however, once the refcount is back above + // zero, then other threads could increase it (happens under heavy contention, when the refcount + // goes to zero in between a load and a refcount increment of a node in try_get, then back up to + // something non-zero, then the refcount increment is done by the other thread) -- so, if the CAS + // to add the node to the actual list fails, decrease the refcount and leave the add operation to + // the next thread who puts the refcount back at zero (which could be us, hence the loop). + auto head = freeListHead.load(std::memory_order_relaxed); + while (true) { + node->freeListNext.store(head, std::memory_order_relaxed); + node->freeListRefs.store(1, std::memory_order_release); + if (!freeListHead.compare_exchange_strong(head, node, std::memory_order_release, std::memory_order_relaxed)) { + // Hmm, the add failed, but we can only try again when the refcount goes back to zero + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST - 1, std::memory_order_release) == 1) { + continue; + } + } + return; + } + } + + private: + // Implemented like a stack, but where node order doesn't matter (nodes are inserted out of order under contention) + std::atomic freeListHead; + + static const std::uint32_t REFS_MASK = 0x7FFFFFFF; + static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000; + +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugMutex mutex; +#endif + }; + + + /////////////////////////// + // Block + /////////////////////////// + + enum InnerQueueContext { implicit_context = 0, explicit_context = 1 }; + + struct Block + { + Block() + : next(nullptr), elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), dynamicallyAllocated(true) + { +#ifdef MCDBGQ_TRACKMEM + owner = nullptr; +#endif + } + + template + inline bool is_empty() const + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Check flags + for (size_t i = 0; i < BLOCK_SIZE; ++i) { + if (!emptyFlags[i].load(std::memory_order_relaxed)) { + return false; + } + } + + // Aha, empty; make sure we have all other memory effects that happened before the empty flags were set + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + else { + // Check counter + if (elementsCompletelyDequeued.load(std::memory_order_relaxed) == BLOCK_SIZE) { + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + assert(elementsCompletelyDequeued.load(std::memory_order_relaxed) <= BLOCK_SIZE); + return false; + } + } + + // Returns true if the block is now empty (does not apply in explicit context) + template + inline bool set_empty(MOODYCAMEL_MAYBE_UNUSED index_t i) + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flag + assert(!emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].load(std::memory_order_relaxed)); + emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].store(true, std::memory_order_release); + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(1, std::memory_order_release); + assert(prevVal < BLOCK_SIZE); + return prevVal == BLOCK_SIZE - 1; + } + } + + // Sets multiple contiguous item statuses to 'empty' (assumes no wrapping and count > 0). + // Returns true if the block is now empty (does not apply in explicit context). + template + inline bool set_many_empty(MOODYCAMEL_MAYBE_UNUSED index_t i, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flags + std::atomic_thread_fence(std::memory_order_release); + i = BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1)) - count + 1; + for (size_t j = 0; j != count; ++j) { + assert(!emptyFlags[i + j].load(std::memory_order_relaxed)); + emptyFlags[i + j].store(true, std::memory_order_relaxed); + } + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(count, std::memory_order_release); + assert(prevVal + count <= BLOCK_SIZE); + return prevVal + count == BLOCK_SIZE; + } + } + + template + inline void set_all_empty() + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set all flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(true, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(BLOCK_SIZE, std::memory_order_relaxed); + } + } + + template + inline void reset_empty() + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Reset flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(false, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(0, std::memory_order_relaxed); + } + } + + inline T* operator[](index_t idx) MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + inline T const* operator[](index_t idx) const MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + + private: + static_assert(std::alignment_of::value <= sizeof(T), "The queue does not support types with an alignment greater than their size at this time"); + MOODYCAMEL_ALIGNED_TYPE_LIKE(char[sizeof(T) * BLOCK_SIZE], T) elements; + public: + Block* next; + std::atomic elementsCompletelyDequeued; + std::atomic emptyFlags[BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD ? BLOCK_SIZE : 1]; + public: + std::atomic freeListRefs; + std::atomic freeListNext; + bool dynamicallyAllocated; // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool' + +#ifdef MCDBGQ_TRACKMEM + void* owner; +#endif + }; + static_assert(std::alignment_of::value >= std::alignment_of::value, "Internal error: Blocks must be at least as aligned as the type they are wrapping"); + + +#ifdef MCDBGQ_TRACKMEM +public: + struct MemStats; +private: +#endif + + /////////////////////////// + // Producer base + /////////////////////////// + + struct ProducerBase : public details::ConcurrentQueueProducerTypelessBase + { + ProducerBase(ConcurrentQueue* parent_, bool isExplicit_) : + tailIndex(0), + headIndex(0), + dequeueOptimisticCount(0), + dequeueOvercommit(0), + tailBlock(nullptr), + isExplicit(isExplicit_), + parent(parent_) + { + } + + virtual ~ProducerBase() { } + + template + inline bool dequeue(U& element) + { + if (isExplicit) { + return static_cast(this)->dequeue(element); + } + else { + return static_cast(this)->dequeue(element); + } + } + + template + inline size_t dequeue_bulk(It& itemFirst, size_t max) + { + if (isExplicit) { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + else { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + } + + inline ProducerBase* next_prod() const { return static_cast(next); } + + inline size_t size_approx() const + { + auto tail = tailIndex.load(std::memory_order_relaxed); + auto head = headIndex.load(std::memory_order_relaxed); + return details::circular_less_than(head, tail) ? static_cast(tail - head) : 0; + } + + inline index_t getTail() const { return tailIndex.load(std::memory_order_relaxed); } + protected: + std::atomic tailIndex; // Where to enqueue to next + std::atomic headIndex; // Where to dequeue from next + + std::atomic dequeueOptimisticCount; + std::atomic dequeueOvercommit; + + Block* tailBlock; + + public: + bool isExplicit; + ConcurrentQueue* parent; + + protected: +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + /////////////////////////// + // Explicit queue + /////////////////////////// + + struct ExplicitProducer : public ProducerBase + { + explicit ExplicitProducer(ConcurrentQueue* parent_) : + ProducerBase(parent_, true), + blockIndex(nullptr), + pr_blockIndexSlotsUsed(0), + pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1), + pr_blockIndexFront(0), + pr_blockIndexEntries(nullptr), + pr_blockIndexRaw(nullptr) + { + size_t poolBasedIndexSize = details::ceil_to_pow_2(parent_->initialBlockPoolSize) >> 1; + if (poolBasedIndexSize > pr_blockIndexSize) { + pr_blockIndexSize = poolBasedIndexSize; + } + + new_block_index(0); // This creates an index with double the number of current entries, i.e. EXPLICIT_INITIAL_INDEX_SIZE + } + + ~ExplicitProducer() + { + // Destruct any elements not yet dequeued. + // Since we're in the destructor, we can assume all elements + // are either completely dequeued or completely not (no halfways). + if (this->tailBlock != nullptr) { // Note this means there must be a block index too + // First find the block that's partially dequeued, if any + Block* halfDequeuedBlock = nullptr; + if ((this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) != 0) { + // The head's not on a block boundary, meaning a block somewhere is partially dequeued + // (or the head block is the tail block and was fully dequeued, but the head/tail are still not on a boundary) + size_t i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & (pr_blockIndexSize - 1); + while (details::circular_less_than(pr_blockIndexEntries[i].base + BLOCK_SIZE, this->headIndex.load(std::memory_order_relaxed))) { + i = (i + 1) & (pr_blockIndexSize - 1); + } + assert(details::circular_less_than(pr_blockIndexEntries[i].base, this->headIndex.load(std::memory_order_relaxed))); + halfDequeuedBlock = pr_blockIndexEntries[i].block; + } + + // Start at the head block (note the first line in the loop gives us the head from the tail on the first iteration) + auto block = this->tailBlock; + do { + block = block->next; + if (block->ConcurrentQueue::Block::template is_empty()) { + continue; + } + + size_t i = 0; // Offset into block + if (block == halfDequeuedBlock) { + i = static_cast(this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + } + + // Walk through all the items in the block; if this is the tail block, we need to stop when we reach the tail index + auto lastValidIndex = (this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) == 0 ? BLOCK_SIZE : static_cast(this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + while (i != BLOCK_SIZE && (block != this->tailBlock || i != lastValidIndex)) { + (*block)[i++]->~T(); + } + } while (block != this->tailBlock); + } + + // Destroy all blocks that we own + if (this->tailBlock != nullptr) { + auto block = this->tailBlock; + do { + auto nextBlock = block->next; + this->parent->add_block_to_free_list(block); + block = nextBlock; + } while (block != this->tailBlock); + } + + // Destroy the block indices + auto header = static_cast(pr_blockIndexRaw); + while (header != nullptr) { + auto prev = static_cast(header->prev); + header->~BlockIndexHeader(); + (Traits::free)(header); + header = prev; + } + } + + template + inline bool enqueue(U&& element) + { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto startBlock = this->tailBlock; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + if (this->tailBlock != nullptr && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + // We can re-use the block ahead of us, it's empty! + this->tailBlock = this->tailBlock->next; + this->tailBlock->ConcurrentQueue::Block::template reset_empty(); + + // We'll put the block on the block index (guaranteed to be room since we're conceptually removing the + // last block from it first -- except instead of removing then adding, we can just overwrite). + // Note that there must be a valid block index here, since even if allocation failed in the ctor, + // it would have been re-attempted when adding the first block to the queue; since there is such + // a block, a block index must have been successfully allocated. + } + else { + // Whatever head value we see here is >= the last value we saw here (relatively), + // and <= its current value. Since we have the most recent tail, the head must be + // <= to it. + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) + || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + // We can't enqueue in another block because there's not enough leeway -- the + // tail could surpass the head by the time the block fills up! (Or we'll exceed + // the size limit, if the second part of the condition was true.) + return false; + } + // We're going to need a new block; check that the block index has room + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize) { + // Hmm, the circular block index is already full -- we'll need + // to allocate a new index. Note pr_blockIndexRaw can only be nullptr if + // the initial allocation failed in the constructor. + + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + return false; + } + else if (!new_block_index(pr_blockIndexSlotsUsed)) { + return false; + } + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + return false; + } +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + ++pr_blockIndexSlotsUsed; + } + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + // The constructor may throw. We want the element not to appear in the queue in + // that case (without corrupting the queue): + MOODYCAMEL_TRY { + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + // Revert change to the current block, but leave the new block available + // for next time + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? this->tailBlock : startBlock; + MOODYCAMEL_RETHROW; + } + } + else { + (void)startBlock; + (void)originalBlockIndexSlotsUsed; + } + + // Add block to block index + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release); + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U& element) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + // Might be something to dequeue, let's give it a try + + // Note that this if is purely for performance purposes in the common case when the queue is + // empty and the values are eventually consistent -- we may enter here spuriously. + + // Note that whatever the values of overcommit and tail are, they are not going to change (unless we + // change them) and must be the same value at this point (inside the if) as when the if condition was + // evaluated. + + // We insert an acquire fence here to synchronize-with the release upon incrementing dequeueOvercommit below. + // This ensures that whatever the value we got loaded into overcommit, the load of dequeueOptisticCount in + // the fetch_add below will result in a value at least as recent as that (and therefore at least as large). + // Note that I believe a compiler (signal) fence here would be sufficient due to the nature of fetch_add (all + // read-modify-write operations are guaranteed to work on the latest value in the modification order), but + // unfortunately that can't be shown to be correct using only the C++11 standard. + // See http://stackoverflow.com/questions/18223161/what-are-the-c11-memory-ordering-guarantees-in-this-corner-case + std::atomic_thread_fence(std::memory_order_acquire); + + // Increment optimistic counter, then check if it went over the boundary + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + + // Note that since dequeueOvercommit must be <= dequeueOptimisticCount (because dequeueOvercommit is only ever + // incremented after dequeueOptimisticCount -- this is enforced in the `else` block below), and since we now + // have a version of dequeueOptimisticCount that is at least as recent as overcommit (due to the release upon + // incrementing dequeueOvercommit and the acquire above that synchronizes with it), overcommit <= myDequeueCount. + // However, we can't assert this since both dequeueOptimisticCount and dequeueOvercommit may (independently) + // overflow; in such a case, though, the logic still holds since the difference between the two is maintained. + + // Note that we reload tail here in case it changed; it will be the same value as before or greater, since + // this load is sequenced after (happens after) the earlier load above. This is supported by read-read + // coherency (as defined in the standard), explained here: http://en.cppreference.com/w/cpp/atomic/memory_order + tail = this->tailIndex.load(std::memory_order_acquire); + if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { + // Guaranteed to be at least one element to dequeue! + + // Get the index. Note that since there's guaranteed to be at least one element, this + // will never exceed tail. We need to do an acquire-release fence here since it's possible + // that whatever condition got us to this point was for an earlier enqueued element (that + // we already see the memory effects for), but that by the time we increment somebody else + // has incremented it, and we need to see the memory effects for *that* element, which is + // in such a case is necessarily visible on the thread that incremented it in the first + // place with the more current condition (they must have acquired a tail that is at least + // as recent). + auto index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + + // Determine which block the element is in + + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + // We need to be careful here about subtracting and dividing because of index wrap-around. + // When an index wraps, we need to preserve the sign of the offset when dividing it by the + // block size (in order to get a correct signed block count offset in all cases): + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto blockBaseIndex = index & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(blockBaseIndex - headBase) / static_cast::type>(BLOCK_SIZE)); + auto block = localBlockIndex->entries[(localBlockIndexHead + offset) & (localBlockIndex->size - 1)].block; + + // Dequeue + auto& el = *((*block)[index]); + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { + // Make sure the element is still fully dequeued and destroyed even if the assignment + // throws + struct Guard { + Block* block; + index_t index; + + ~Guard() + { + (*block)[index]->~T(); + block->ConcurrentQueue::Block::template set_empty(index); + } + } guard = { block, index }; + + element = std::move(el); // NOLINT + } + else { + element = std::move(el); // NOLINT + el.~T(); // NOLINT + block->ConcurrentQueue::Block::template set_empty(index); + } + + return true; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); // Release so that the fetch_add on dequeueOptimisticCount is guaranteed to happen before this write + } + } + + return false; + } + + template + bool MOODYCAMEL_NO_TSAN enqueue_bulk(It itemFirst, size_t count) + { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + auto originalBlockIndexFront = pr_blockIndexFront; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + + Block* firstAllocatedBlock = nullptr; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { + // Allocate as many blocks as possible from ahead + while (blockBaseDiff > 0 && this->tailBlock != nullptr && this->tailBlock->next != firstAllocatedBlock && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + this->tailBlock = this->tailBlock->next; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Now allocate as many blocks as necessary from the block pool + while (blockBaseDiff > 0) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize || full) { + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + // Failed to allocate, undo changes (but keep injected blocks) + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + else if (full || !new_block_index(originalBlockIndexSlotsUsed)) { + // Failed to allocate, undo changes (but keep injected blocks) + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + + // pr_blockIndexFront is updated inside new_block_index, so we need to + // update our fallback value too (since we keep the new index even if we + // later fail) + originalBlockIndexFront = originalBlockIndexSlotsUsed; + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template set_all_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + ++pr_blockIndexSlotsUsed; + + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Excellent, all allocations succeeded. Reset each block's emptiness before we fill them up, and + // publish the new block index front + auto block = firstAllocatedBlock; + while (true) { + block->ConcurrentQueue::Block::template reset_empty(); + if (block == this->tailBlock) { + break; + } + block = block->next; + } + + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + auto endBlock = this->tailBlock; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } + else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + // Must use copy constructor even if move constructor is available + // because we may have to revert if there's an exception. + // Sorry about the horrible templated next line, but it was the only way + // to disable moving *at compile time*, which is important because a type + // may only define a (noexcept) move constructor, and so calls to the + // cctor will not compile, even if they are in an if branch that will never + // be executed + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if(nullptr)) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + // Oh dear, an exception's been thrown -- destroy the elements that + // were enqueued so far and revert the entire bulk operation (we'll keep + // any allocated blocks in our linked list for later, though). + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + if (firstAllocatedBlock != nullptr) + blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + size_t dequeue_bulk(It& itemFirst, size_t max) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Determine which block the first element is in + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto firstBlockBaseIndex = firstIndex & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(firstBlockBaseIndex - headBase) / static_cast::type>(BLOCK_SIZE)); + auto indexIndex = (localBlockIndexHead + offset) & (localBlockIndex->size - 1); + + // Iterate the blocks and dequeue + auto index = firstIndex; + do { + auto firstIndexInBlock = index; + index_t endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + auto block = localBlockIndex->entries[indexIndex].block; + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } + else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + // It's too late to revert the dequeue, but we can make sure that all + // the dequeued objects are properly destroyed and the block index + // (and empty count) are properly updated before we propagate the exception + do { + block = localBlockIndex->entries[indexIndex].block; + while (index != endIndex) { + (*block)[index++]->~T(); + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + + firstIndexInBlock = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + struct BlockIndexEntry + { + index_t base; + Block* block; + }; + + struct BlockIndexHeader + { + size_t size; + std::atomic front; // Current slot (not next, like pr_blockIndexFront) + BlockIndexEntry* entries; + void* prev; + }; + + + bool new_block_index(size_t numberOfFilledSlotsToExpose) + { + auto prevBlockSizeMask = pr_blockIndexSize - 1; + + // Create the new block + pr_blockIndexSize <<= 1; + auto newRawPtr = static_cast((Traits::malloc)(sizeof(BlockIndexHeader) + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * pr_blockIndexSize)); + if (newRawPtr == nullptr) { + pr_blockIndexSize >>= 1; // Reset to allow graceful retry + return false; + } + + auto newBlockIndexEntries = reinterpret_cast(details::align_for(newRawPtr + sizeof(BlockIndexHeader))); + + // Copy in all the old indices, if any + size_t j = 0; + if (pr_blockIndexSlotsUsed != 0) { + auto i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & prevBlockSizeMask; + do { + newBlockIndexEntries[j++] = pr_blockIndexEntries[i]; + i = (i + 1) & prevBlockSizeMask; + } while (i != pr_blockIndexFront); + } + + // Update everything + auto header = new (newRawPtr) BlockIndexHeader; + header->size = pr_blockIndexSize; + header->front.store(numberOfFilledSlotsToExpose - 1, std::memory_order_relaxed); + header->entries = newBlockIndexEntries; + header->prev = pr_blockIndexRaw; // we link the new block to the old one so we can free it later + + pr_blockIndexFront = j; + pr_blockIndexEntries = newBlockIndexEntries; + pr_blockIndexRaw = newRawPtr; + blockIndex.store(header, std::memory_order_release); + + return true; + } + + private: + std::atomic blockIndex; + + // To be used by producer only -- consumer must use the ones in referenced by blockIndex + size_t pr_blockIndexSlotsUsed; + size_t pr_blockIndexSize; + size_t pr_blockIndexFront; // Next slot (not current) + BlockIndexEntry* pr_blockIndexEntries; + void* pr_blockIndexRaw; + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ExplicitProducer* nextExplicitProducer; + private: +#endif + +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Implicit queue + ////////////////////////////////// + + struct ImplicitProducer : public ProducerBase + { + ImplicitProducer(ConcurrentQueue* parent_) : + ProducerBase(parent_, false), + nextBlockIndexCapacity(IMPLICIT_INITIAL_INDEX_SIZE), + blockIndex(nullptr) + { + new_block_index(); + } + + ~ImplicitProducer() + { + // Note that since we're in the destructor we can assume that all enqueue/dequeue operations + // completed already; this means that all undequeued elements are placed contiguously across + // contiguous blocks, and that only the first and last remaining blocks can be only partially + // empty (all other remaining blocks must be completely full). + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + // Unregister ourselves for thread termination notification + if (!this->inactive.load(std::memory_order_relaxed)) { + details::ThreadExitNotifier::unsubscribe(&threadExitListener); + } +#endif + + // Destroy all remaining elements! + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto index = this->headIndex.load(std::memory_order_relaxed); + Block* block = nullptr; + assert(index == tail || details::circular_less_than(index, tail)); + bool forceFreeLastBlock = index != tail; // If we enter the loop, then the last (tail) block will not be freed + while (index != tail) { + if ((index & static_cast(BLOCK_SIZE - 1)) == 0 || block == nullptr) { + if (block != nullptr) { + // Free the old block + this->parent->add_block_to_free_list(block); + } + + block = get_block_index_entry_for_index(index)->value.load(std::memory_order_relaxed); + } + + ((*block)[index])->~T(); + ++index; + } + // Even if the queue is empty, there's still one block that's not on the free list + // (unless the head index reached the end of it, in which case the tail will be poised + // to create a new block). + if (this->tailBlock != nullptr && (forceFreeLastBlock || (tail & static_cast(BLOCK_SIZE - 1)) != 0)) { + this->parent->add_block_to_free_list(this->tailBlock); + } + + // Destroy block index + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + if (localBlockIndex != nullptr) { + for (size_t i = 0; i != localBlockIndex->capacity; ++i) { + localBlockIndex->index[i]->~BlockIndexEntry(); + } + do { + auto prev = localBlockIndex->prev; + localBlockIndex->~BlockIndexHeader(); + (Traits::free)(localBlockIndex); + localBlockIndex = prev; + } while (localBlockIndex != nullptr); + } + } + + template + inline bool enqueue(U&& element) + { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + return false; + } +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Find out where we'll be inserting this block in the block index + BlockIndexEntry* idxEntry; + if (!insert_block_index_entry(idxEntry, currentTailIndex)) { + return false; + } + + // Get ahold of a new block + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + return false; + } +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + // May throw, try to insert now before we publish the fact that we have this new block + MOODYCAMEL_TRY { + new ((*newBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(newBlock); + MOODYCAMEL_RETHROW; + } + } + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + this->tailBlock = newBlock; + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U& element) + { + // See ExplicitProducer::dequeue for rationale and explanation + index_t tail = this->tailIndex.load(std::memory_order_relaxed); + index_t overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + std::atomic_thread_fence(std::memory_order_acquire); + + index_t myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + tail = this->tailIndex.load(std::memory_order_acquire); + if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { + index_t index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + // Determine which block the element is in + auto entry = get_block_index_entry_for_index(index); + + // Dequeue + auto block = entry->value.load(std::memory_order_relaxed); + auto& el = *((*block)[index]); + + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + // Note: Acquiring the mutex with every dequeue instead of only when a block + // is released is very sub-optimal, but it is, after all, purely debug code. + debug::DebugLock lock(producer->mutex); +#endif + struct Guard { + Block* block; + index_t index; + BlockIndexEntry* entry; + ConcurrentQueue* parent; + + ~Guard() + { + (*block)[index]->~T(); + if (block->ConcurrentQueue::Block::template set_empty(index)) { + entry->value.store(nullptr, std::memory_order_relaxed); + parent->add_block_to_free_list(block); + } + } + } guard = { block, index, entry, this->parent }; + + element = std::move(el); // NOLINT + } + else { + element = std::move(el); // NOLINT + el.~T(); // NOLINT + + if (block->ConcurrentQueue::Block::template set_empty(index)) { + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Add the block back into the global free pool (and remove from block index) + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + } + + return true; + } + else { + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); + } + } + + return false; + } + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4706) // assignment within conditional expression +#endif + template + bool enqueue_bulk(It itemFirst, size_t count) + { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + + // Note that the tailBlock we start off with may not be owned by us any more; + // this happens if it was filled up exactly to the top (setting tailIndex to + // the first index of the next block which is not yet allocated), then dequeued + // completely (putting it on the free list) before we enqueue again. + + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + Block* firstAllocatedBlock = nullptr; + auto endBlock = this->tailBlock; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + do { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + // Find out where we'll be inserting this block in the block index + BlockIndexEntry* idxEntry = nullptr; // initialization here unnecessary but compiler can't always tell + Block* newBlock; + bool indexInserted = false; + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + + if (full || !(indexInserted = insert_block_index_entry(idxEntry, currentTailIndex)) || (newBlock = this->parent->ConcurrentQueue::template requisition_block()) == nullptr) { + // Index allocation or block allocation failed; revert any other allocations + // and index insertions done so far for this operation + if (indexInserted) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + } + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + + return false; + } + +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + newBlock->next = nullptr; + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + // Store the chain of blocks so that we can undo if later allocations fail, + // and so that we can find the blocks when we do the actual enqueueing + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr) { + assert(this->tailBlock != nullptr); + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + endBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? newBlock : firstAllocatedBlock; + } while (blockBaseDiff > 0); + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } + else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if(nullptr)) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + auto idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + template + size_t dequeue_bulk(It& itemFirst, size_t max) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Iterate the blocks and dequeue + auto index = firstIndex; + BlockIndexHeader* localBlockIndex; + auto indexIndex = get_block_index_index_for_index(index, localBlockIndex); + do { + auto blockStartIndex = index; + index_t endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + + auto entry = localBlockIndex->index[indexIndex]; + auto block = entry->value.load(std::memory_order_relaxed); + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } + else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + do { + entry = localBlockIndex->index[indexIndex]; + block = entry->value.load(std::memory_order_relaxed); + while (index != endIndex) { + (*block)[index++]->~T(); + } + + if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + entry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(block); + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + + blockStartIndex = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Note that the set_many_empty above did a release, meaning that anybody who acquires the block + // we're about to free can use it safely since our writes (and reads!) will have happened-before then. + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + // The block size must be > 1, so any number with the low bit set is an invalid block base index + static const index_t INVALID_BLOCK_BASE = 1; + + struct BlockIndexEntry + { + std::atomic key; + std::atomic value; + }; + + struct BlockIndexHeader + { + size_t capacity; + std::atomic tail; + BlockIndexEntry* entries; + BlockIndexEntry** index; + BlockIndexHeader* prev; + }; + + template + inline bool insert_block_index_entry(BlockIndexEntry*& idxEntry, index_t blockStartIndex) + { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); // We're the only writer thread, relaxed is OK + if (localBlockIndex == nullptr) { + return false; // this can happen if new_block_index failed in the constructor + } + size_t newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + if (idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE || + idxEntry->value.load(std::memory_order_relaxed) == nullptr) { + + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + + // No room in the old block index, try to allocate another one! + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + return false; + } + else if (!new_block_index()) { + return false; + } + else { + localBlockIndex = blockIndex.load(std::memory_order_relaxed); + newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + assert(idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE); + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + } + + inline void rewind_block_index_tail() + { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + localBlockIndex->tail.store((localBlockIndex->tail.load(std::memory_order_relaxed) - 1) & (localBlockIndex->capacity - 1), std::memory_order_relaxed); + } + + inline BlockIndexEntry* get_block_index_entry_for_index(index_t index) const + { + BlockIndexHeader* localBlockIndex; + auto idx = get_block_index_index_for_index(index, localBlockIndex); + return localBlockIndex->index[idx]; + } + + inline size_t get_block_index_index_for_index(index_t index, BlockIndexHeader*& localBlockIndex) const + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + index &= ~static_cast(BLOCK_SIZE - 1); + localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto tail = localBlockIndex->tail.load(std::memory_order_acquire); + auto tailBase = localBlockIndex->index[tail]->key.load(std::memory_order_relaxed); + assert(tailBase != INVALID_BLOCK_BASE); + // Note: Must use division instead of shift because the index may wrap around, causing a negative + // offset, whose negativity we want to preserve + auto offset = static_cast(static_cast::type>(index - tailBase) / static_cast::type>(BLOCK_SIZE)); + size_t idx = (tail + offset) & (localBlockIndex->capacity - 1); + assert(localBlockIndex->index[idx]->key.load(std::memory_order_relaxed) == index && localBlockIndex->index[idx]->value.load(std::memory_order_relaxed) != nullptr); + return idx; + } + + bool new_block_index() + { + auto prev = blockIndex.load(std::memory_order_relaxed); + size_t prevCapacity = prev == nullptr ? 0 : prev->capacity; + auto entryCount = prev == nullptr ? nextBlockIndexCapacity : prevCapacity; + auto raw = static_cast((Traits::malloc)( + sizeof(BlockIndexHeader) + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * entryCount + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry*) * nextBlockIndexCapacity)); + if (raw == nullptr) { + return false; + } + + auto header = new (raw) BlockIndexHeader; + auto entries = reinterpret_cast(details::align_for(raw + sizeof(BlockIndexHeader))); + auto index = reinterpret_cast(details::align_for(reinterpret_cast(entries) + sizeof(BlockIndexEntry) * entryCount)); + if (prev != nullptr) { + auto prevTail = prev->tail.load(std::memory_order_relaxed); + auto prevPos = prevTail; + size_t i = 0; + do { + prevPos = (prevPos + 1) & (prev->capacity - 1); + index[i++] = prev->index[prevPos]; + } while (prevPos != prevTail); + assert(i == prevCapacity); + } + for (size_t i = 0; i != entryCount; ++i) { + new (entries + i) BlockIndexEntry; + entries[i].key.store(INVALID_BLOCK_BASE, std::memory_order_relaxed); + index[prevCapacity + i] = entries + i; + } + header->prev = prev; + header->entries = entries; + header->index = index; + header->capacity = nextBlockIndexCapacity; + header->tail.store((prevCapacity - 1) & (nextBlockIndexCapacity - 1), std::memory_order_relaxed); + + blockIndex.store(header, std::memory_order_release); + + nextBlockIndexCapacity <<= 1; + + return true; + } + + private: + size_t nextBlockIndexCapacity; + std::atomic blockIndex; + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + public: + details::ThreadExitListener threadExitListener; + private: +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ImplicitProducer* nextImplicitProducer; + private: +#endif + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + mutable debug::DebugMutex mutex; +#endif +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Block pool manipulation + ////////////////////////////////// + + void populate_initial_block_list(size_t blockCount) + { + initialBlockPoolSize = blockCount; + if (initialBlockPoolSize == 0) { + initialBlockPool = nullptr; + return; + } + + initialBlockPool = create_array(blockCount); + if (initialBlockPool == nullptr) { + initialBlockPoolSize = 0; + } + for (size_t i = 0; i < initialBlockPoolSize; ++i) { + initialBlockPool[i].dynamicallyAllocated = false; + } + } + + inline Block* try_get_block_from_initial_pool() + { + if (initialBlockPoolIndex.load(std::memory_order_relaxed) >= initialBlockPoolSize) { + return nullptr; + } + + auto index = initialBlockPoolIndex.fetch_add(1, std::memory_order_relaxed); + + return index < initialBlockPoolSize ? (initialBlockPool + index) : nullptr; + } + + inline void add_block_to_free_list(Block* block) + { +#ifdef MCDBGQ_TRACKMEM + block->owner = nullptr; +#endif + if (!Traits::RECYCLE_ALLOCATED_BLOCKS && block->dynamicallyAllocated) { + destroy(block); + } + else { + freeList.add(block); + } + } + + inline void add_blocks_to_free_list(Block* block) + { + while (block != nullptr) { + auto next = block->next; + add_block_to_free_list(block); + block = next; + } + } + + inline Block* try_get_block_from_free_list() + { + return freeList.try_get(); + } + + // Gets a free block from one of the memory pools, or allocates a new one (if applicable) + template + Block* requisition_block() + { + auto block = try_get_block_from_initial_pool(); + if (block != nullptr) { + return block; + } + + block = try_get_block_from_free_list(); + if (block != nullptr) { + return block; + } + + MOODYCAMEL_CONSTEXPR_IF (canAlloc == CanAlloc) { + return create(); + } + else { + return nullptr; + } + } + + +#ifdef MCDBGQ_TRACKMEM + public: + struct MemStats { + size_t allocatedBlocks; + size_t usedBlocks; + size_t freeBlocks; + size_t ownedBlocksExplicit; + size_t ownedBlocksImplicit; + size_t implicitProducers; + size_t explicitProducers; + size_t elementsEnqueued; + size_t blockClassBytes; + size_t queueClassBytes; + size_t implicitBlockIndexBytes; + size_t explicitBlockIndexBytes; + + friend class ConcurrentQueue; + + private: + static MemStats getFor(ConcurrentQueue* q) + { + MemStats stats = { 0 }; + + stats.elementsEnqueued = q->size_approx(); + + auto block = q->freeList.head_unsafe(); + while (block != nullptr) { + ++stats.allocatedBlocks; + ++stats.freeBlocks; + block = block->freeListNext.load(std::memory_order_relaxed); + } + + for (auto ptr = q->producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + bool implicit = dynamic_cast(ptr) != nullptr; + stats.implicitProducers += implicit ? 1 : 0; + stats.explicitProducers += implicit ? 0 : 1; + + if (implicit) { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ImplicitProducer); + auto head = prod->headIndex.load(std::memory_order_relaxed); + auto tail = prod->tailIndex.load(std::memory_order_relaxed); + auto hash = prod->blockIndex.load(std::memory_order_relaxed); + if (hash != nullptr) { + for (size_t i = 0; i != hash->capacity; ++i) { + if (hash->index[i]->key.load(std::memory_order_relaxed) != ImplicitProducer::INVALID_BLOCK_BASE && hash->index[i]->value.load(std::memory_order_relaxed) != nullptr) { + ++stats.allocatedBlocks; + ++stats.ownedBlocksImplicit; + } + } + stats.implicitBlockIndexBytes += hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry); + for (; hash != nullptr; hash = hash->prev) { + stats.implicitBlockIndexBytes += sizeof(typename ImplicitProducer::BlockIndexHeader) + hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry*); + } + } + for (; details::circular_less_than(head, tail); head += BLOCK_SIZE) { + //auto block = prod->get_block_index_entry_for_index(head); + ++stats.usedBlocks; + } + } + else { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ExplicitProducer); + auto tailBlock = prod->tailBlock; + bool wasNonEmpty = false; + if (tailBlock != nullptr) { + auto block = tailBlock; + do { + ++stats.allocatedBlocks; + if (!block->ConcurrentQueue::Block::template is_empty() || wasNonEmpty) { + ++stats.usedBlocks; + wasNonEmpty = wasNonEmpty || block != tailBlock; + } + ++stats.ownedBlocksExplicit; + block = block->next; + } while (block != tailBlock); + } + auto index = prod->blockIndex.load(std::memory_order_relaxed); + while (index != nullptr) { + stats.explicitBlockIndexBytes += sizeof(typename ExplicitProducer::BlockIndexHeader) + index->size * sizeof(typename ExplicitProducer::BlockIndexEntry); + index = static_cast(index->prev); + } + } + } + + auto freeOnInitialPool = q->initialBlockPoolIndex.load(std::memory_order_relaxed) >= q->initialBlockPoolSize ? 0 : q->initialBlockPoolSize - q->initialBlockPoolIndex.load(std::memory_order_relaxed); + stats.allocatedBlocks += freeOnInitialPool; + stats.freeBlocks += freeOnInitialPool; + + stats.blockClassBytes = sizeof(Block) * stats.allocatedBlocks; + stats.queueClassBytes += sizeof(ConcurrentQueue); + + return stats; + } + }; + + // For debugging only. Not thread-safe. + MemStats getMemStats() + { + return MemStats::getFor(this); + } + private: + friend struct MemStats; +#endif + + + ////////////////////////////////// + // Producer list manipulation + ////////////////////////////////// + + ProducerBase* recycle_or_create_producer(bool isExplicit) + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + // Try to re-use one first + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->inactive.load(std::memory_order_relaxed) && ptr->isExplicit == isExplicit) { + bool expected = true; + if (ptr->inactive.compare_exchange_strong(expected, /* desired */ false, std::memory_order_acquire, std::memory_order_relaxed)) { + // We caught one! It's been marked as activated, the caller can have it + return ptr; + } + } + } + + return add_producer(isExplicit ? static_cast(create(this)) : create(this)); + } + + ProducerBase* add_producer(ProducerBase* producer) + { + // Handle failed memory allocation + if (producer == nullptr) { + return nullptr; + } + + producerCount.fetch_add(1, std::memory_order_relaxed); + + // Add it to the lock-free list + auto prevTail = producerListTail.load(std::memory_order_relaxed); + do { + producer->next = prevTail; + } while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, std::memory_order_relaxed)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + if (producer->isExplicit) { + auto prevTailExplicit = explicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextExplicitProducer = prevTailExplicit; + } while (!explicitProducers.compare_exchange_weak(prevTailExplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } + else { + auto prevTailImplicit = implicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextImplicitProducer = prevTailImplicit; + } while (!implicitProducers.compare_exchange_weak(prevTailImplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } +#endif + + return producer; + } + + void reown_producers() + { + // After another instance is moved-into/swapped-with this one, all the + // producers we stole still think their parents are the other queue. + // So fix them up! + for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) { + ptr->parent = this; + } + } + + + ////////////////////////////////// + // Implicit producer hash + ////////////////////////////////// + + struct ImplicitProducerKVP + { + std::atomic key; + ImplicitProducer* value; // No need for atomicity since it's only read by the thread that sets it in the first place + + ImplicitProducerKVP() : value(nullptr) { } + + ImplicitProducerKVP(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + { + key.store(other.key.load(std::memory_order_relaxed), std::memory_order_relaxed); + value = other.value; + } + + inline ImplicitProducerKVP& operator=(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + inline void swap(ImplicitProducerKVP& other) MOODYCAMEL_NOEXCEPT + { + if (this != &other) { + details::swap_relaxed(key, other.key); + std::swap(value, other.value); + } + } + }; + + template + friend void moodycamel::swap(typename ConcurrentQueue::ImplicitProducerKVP&, typename ConcurrentQueue::ImplicitProducerKVP&) MOODYCAMEL_NOEXCEPT; + + struct ImplicitProducerHash + { + size_t capacity; + ImplicitProducerKVP* entries; + ImplicitProducerHash* prev; + }; + + inline void populate_initial_implicit_producer_hash() + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { + return; + } + else { + implicitProducerHashCount.store(0, std::memory_order_relaxed); + auto hash = &initialImplicitProducerHash; + hash->capacity = INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; + hash->entries = &initialImplicitProducerHashEntries[0]; + for (size_t i = 0; i != INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; ++i) { + initialImplicitProducerHashEntries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + hash->prev = nullptr; + implicitProducerHash.store(hash, std::memory_order_relaxed); + } + } + + void swap_implicit_producer_hashes(ConcurrentQueue& other) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { + return; + } + else { + // Swap (assumes our implicit producer hash is initialized) + initialImplicitProducerHashEntries.swap(other.initialImplicitProducerHashEntries); + initialImplicitProducerHash.entries = &initialImplicitProducerHashEntries[0]; + other.initialImplicitProducerHash.entries = &other.initialImplicitProducerHashEntries[0]; + + details::swap_relaxed(implicitProducerHashCount, other.implicitProducerHashCount); + + details::swap_relaxed(implicitProducerHash, other.implicitProducerHash); + if (implicitProducerHash.load(std::memory_order_relaxed) == &other.initialImplicitProducerHash) { + implicitProducerHash.store(&initialImplicitProducerHash, std::memory_order_relaxed); + } + else { + ImplicitProducerHash* hash; + for (hash = implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &other.initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &initialImplicitProducerHash; + } + if (other.implicitProducerHash.load(std::memory_order_relaxed) == &initialImplicitProducerHash) { + other.implicitProducerHash.store(&other.initialImplicitProducerHash, std::memory_order_relaxed); + } + else { + ImplicitProducerHash* hash; + for (hash = other.implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &other.initialImplicitProducerHash; + } + } + } + + // Only fails (returns nullptr) if memory allocation fails + ImplicitProducer* get_or_add_implicit_producer() + { + // Note that since the data is essentially thread-local (key is thread ID), + // there's a reduced need for fences (memory ordering is already consistent + // for any individual thread), except for the current table itself. + + // Start by looking for the thread ID in the current and all previous hash tables. + // If it's not found, it must not be in there yet, since this same thread would + // have added it previously to one of the tables that we traversed. + + // Code and algorithm adapted from http://preshing.com/20130605/the-worlds-simplest-lock-free-hash-table + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + + auto mainHash = implicitProducerHash.load(std::memory_order_acquire); + assert(mainHash != nullptr); // silence clang-tidy and MSVC warnings (hash cannot be null) + for (auto hash = mainHash; hash != nullptr; hash = hash->prev) { + // Look for the id in this hash + auto index = hashedId; + while (true) { // Not an infinite loop because at least one slot is free in the hash table + index &= hash->capacity - 1u; + + auto probedKey = hash->entries[index].key.load(std::memory_order_relaxed); + if (probedKey == id) { + // Found it! If we had to search several hashes deep, though, we should lazily add it + // to the current main hash table to avoid the extended search next time. + // Note there's guaranteed to be room in the current hash table since every subsequent + // table implicitly reserves space for all previous tables (there's only one + // implicitProducerHashCount). + auto value = hash->entries[index].value; + if (hash != mainHash) { + index = hashedId; + while (true) { + index &= mainHash->capacity - 1u; + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed) || + mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { +#else + if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { +#endif + mainHash->entries[index].value = value; + break; + } + ++index; + } + } + + return value; + } + if (probedKey == details::invalid_thread_id) { + break; // Not in this hash table + } + ++index; + } + } + + // Insert! + auto newCount = 1 + implicitProducerHashCount.fetch_add(1, std::memory_order_relaxed); + while (true) { + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + if (newCount >= (mainHash->capacity >> 1) && !implicitProducerHashResizeInProgress.test_and_set(std::memory_order_acquire)) { + // We've acquired the resize lock, try to allocate a bigger hash table. + // Note the acquire fence synchronizes with the release fence at the end of this block, and hence when + // we reload implicitProducerHash it must be the most recent version (it only gets changed within this + // locked block). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + if (newCount >= (mainHash->capacity >> 1)) { + size_t newCapacity = mainHash->capacity << 1; + while (newCount >= (newCapacity >> 1)) { + newCapacity <<= 1; + } + auto raw = static_cast((Traits::malloc)(sizeof(ImplicitProducerHash) + std::alignment_of::value - 1 + sizeof(ImplicitProducerKVP) * newCapacity)); + if (raw == nullptr) { + // Allocation failed + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + return nullptr; + } + + auto newHash = new (raw) ImplicitProducerHash; + newHash->capacity = static_cast(newCapacity); + newHash->entries = reinterpret_cast(details::align_for(raw + sizeof(ImplicitProducerHash))); + for (size_t i = 0; i != newCapacity; ++i) { + new (newHash->entries + i) ImplicitProducerKVP; + newHash->entries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + newHash->prev = mainHash; + implicitProducerHash.store(newHash, std::memory_order_release); + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + mainHash = newHash; + } + else { + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + } + } + + // If it's < three-quarters full, add to the old one anyway so that we don't have to wait for the next table + // to finish being allocated by another thread (and if we just finished allocating above, the condition will + // always be true) + if (newCount < (mainHash->capacity >> 1) + (mainHash->capacity >> 2)) { + auto producer = static_cast(recycle_or_create_producer(false)); + if (producer == nullptr) { + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + return nullptr; + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + producer->threadExitListener.callback = &ConcurrentQueue::implicit_producer_thread_exited_callback; + producer->threadExitListener.userData = producer; + details::ThreadExitNotifier::subscribe(&producer->threadExitListener); +#endif + + auto index = hashedId; + while (true) { + index &= mainHash->capacity - 1u; + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if (mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); // already counted as a used slot + mainHash->entries[index].value = producer; + break; + } +#endif + if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { + mainHash->entries[index].value = producer; + break; + } + ++index; + } + return producer; + } + + // Hmm, the old hash is quite full and somebody else is busy allocating a new one. + // We need to wait for the allocating thread to finish (if it succeeds, we add, if not, + // we try to allocate ourselves). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + } + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + void implicit_producer_thread_exited(ImplicitProducer* producer) + { + // Remove from hash +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + auto hash = implicitProducerHash.load(std::memory_order_acquire); + assert(hash != nullptr); // The thread exit listener is only registered if we were added to a hash in the first place + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + details::thread_id_t probedKey; + + // We need to traverse all the hashes just in case other threads aren't on the current one yet and are + // trying to add an entry thinking there's a free slot (because they reused a producer) + for (; hash != nullptr; hash = hash->prev) { + auto index = hashedId; + do { + index &= hash->capacity - 1u; + probedKey = id; + if (hash->entries[index].key.compare_exchange_strong(probedKey, details::invalid_thread_id2, std::memory_order_seq_cst, std::memory_order_relaxed)) { + break; + } + ++index; + } while (probedKey != details::invalid_thread_id); // Can happen if the hash has changed but we weren't put back in it yet, or if we weren't added to this hash in the first place + } + + // Mark the queue as being recyclable + producer->inactive.store(true, std::memory_order_release); + } + + static void implicit_producer_thread_exited_callback(void* userData) + { + auto producer = static_cast(userData); + auto queue = producer->parent; + queue->implicit_producer_thread_exited(producer); + } +#endif + + ////////////////////////////////// + // Utility functions + ////////////////////////////////// + + template + static inline void* aligned_malloc(size_t size) + { + MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= std::alignment_of::value) + return (Traits::malloc)(size); + else { + size_t alignment = std::alignment_of::value; + void* raw = (Traits::malloc)(size + alignment - 1 + sizeof(void*)); + if (!raw) + return nullptr; + char* ptr = details::align_for(reinterpret_cast(raw) + sizeof(void*)); + *(reinterpret_cast(ptr) - 1) = raw; + return ptr; + } + } + + template + static inline void aligned_free(void* ptr) + { + MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= std::alignment_of::value) + return (Traits::free)(ptr); + else + (Traits::free)(ptr ? *(reinterpret_cast(ptr) - 1) : nullptr); + } + + template + static inline U* create_array(size_t count) + { + assert(count > 0); + U* p = static_cast(aligned_malloc(sizeof(U) * count)); + if (p == nullptr) + return nullptr; + + for (size_t i = 0; i != count; ++i) + new (p + i) U(); + return p; + } + + template + static inline void destroy_array(U* p, size_t count) + { + if (p != nullptr) { + assert(count > 0); + for (size_t i = count; i != 0; ) + (p + --i)->~U(); + } + aligned_free(p); + } + + template + static inline U* create() + { + void* p = aligned_malloc(sizeof(U)); + return p != nullptr ? new (p) U : nullptr; + } + + template + static inline U* create(A1&& a1) + { + void* p = aligned_malloc(sizeof(U)); + return p != nullptr ? new (p) U(std::forward(a1)) : nullptr; + } + + template + static inline void destroy(U* p) + { + if (p != nullptr) + p->~U(); + aligned_free(p); + } + +private: + std::atomic producerListTail; + std::atomic producerCount; + + std::atomic initialBlockPoolIndex; + Block* initialBlockPool; + size_t initialBlockPoolSize; + +#ifndef MCDBGQ_USEDEBUGFREELIST + FreeList freeList; +#else + debug::DebugFreeList freeList; +#endif + + std::atomic implicitProducerHash; + std::atomic implicitProducerHashCount; // Number of slots logically used + ImplicitProducerHash initialImplicitProducerHash; + std::array initialImplicitProducerHashEntries; + std::atomic_flag implicitProducerHashResizeInProgress; + + std::atomic nextExplicitConsumerId; + std::atomic globalExplicitConsumerOffset; + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugMutex implicitProdMutex; +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + std::atomic explicitProducers; + std::atomic implicitProducers; +#endif +}; + + +template +ProducerToken::ProducerToken(ConcurrentQueue& queue) + : producer(queue.recycle_or_create_producer(true)) +{ + if (producer != nullptr) { + producer->token = this; + } +} + +template +ProducerToken::ProducerToken(BlockingConcurrentQueue& queue) + : producer(reinterpret_cast*>(&queue)->recycle_or_create_producer(true)) +{ + if (producer != nullptr) { + producer->token = this; + } +} + +template +ConsumerToken::ConsumerToken(ConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = queue.nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = static_cast(-1); +} + +template +ConsumerToken::ConsumerToken(BlockingConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = reinterpret_cast*>(&queue)->nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = static_cast(-1); +} + +template +inline void swap(ConcurrentQueue& a, ConcurrentQueue& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +inline void swap(ProducerToken& a, ProducerToken& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +inline void swap(ConsumerToken& a, ConsumerToken& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +} + +#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) +#pragma warning(pop) +#endif + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +#pragma GCC diagnostic pop +#endif diff --git a/thirdparty/lightweightsemaphore.h b/thirdparty/lightweightsemaphore.h new file mode 100644 index 0000000..41ba094 --- /dev/null +++ b/thirdparty/lightweightsemaphore.h @@ -0,0 +1,425 @@ +// Provides an efficient implementation of a semaphore (LightweightSemaphore). +// This is an extension of Jeff Preshing's sempahore implementation (licensed +// under the terms of its separate zlib license) that has been adapted and +// extended by Cameron Desrochers. + +#pragma once + +#include // For std::size_t +#include +#include // For std::make_signed + +#if defined(_WIN32) +// Avoid including windows.h in a header; we only need a handful of +// items, so we'll redeclare them here (this is relatively safe since +// the API generally has to remain stable between Windows versions). +// I know this is an ugly hack but it still beats polluting the global +// namespace with thousands of generic names or adding a .cpp for nothing. +extern "C" { + struct _SECURITY_ATTRIBUTES; + __declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName); + __declspec(dllimport) int __stdcall CloseHandle(void* hObject); + __declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds); + __declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount); +} +#elif defined(__MACH__) +#include +#elif defined(__unix__) +#include + +#if defined(__GLIBC_PREREQ) && defined(_GNU_SOURCE) +#if __GLIBC_PREREQ(2,30) +#define MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC +#endif +#endif +#endif + +namespace moodycamel +{ +namespace details +{ + +// Code in the mpmc_sema namespace below is an adaptation of Jeff Preshing's +// portable + lightweight semaphore implementations, originally from +// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h +// LICENSE: +// Copyright (c) 2015 Jeff Preshing +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgement in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +#if defined(_WIN32) +class Semaphore +{ +private: + void* m_hSema; + + Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + +public: + Semaphore(int initialCount = 0) + { + assert(initialCount >= 0); + const long maxLong = 0x7fffffff; + m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr); + assert(m_hSema); + } + + ~Semaphore() + { + CloseHandle(m_hSema); + } + + bool wait() + { + const unsigned long infinite = 0xffffffff; + return WaitForSingleObject(m_hSema, infinite) == 0; + } + + bool try_wait() + { + return WaitForSingleObject(m_hSema, 0) == 0; + } + + bool timed_wait(std::uint64_t usecs) + { + return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0; + } + + void signal(int count = 1) + { + while (!ReleaseSemaphore(m_hSema, count, nullptr)); + } +}; +#elif defined(__MACH__) +//--------------------------------------------------------- +// Semaphore (Apple iOS and OSX) +// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html +//--------------------------------------------------------- +class Semaphore +{ +private: + semaphore_t m_sema; + + Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + +public: + Semaphore(int initialCount = 0) + { + assert(initialCount >= 0); + kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount); + assert(rc == KERN_SUCCESS); + (void)rc; + } + + ~Semaphore() + { + semaphore_destroy(mach_task_self(), m_sema); + } + + bool wait() + { + return semaphore_wait(m_sema) == KERN_SUCCESS; + } + + bool try_wait() + { + return timed_wait(0); + } + + bool timed_wait(std::uint64_t timeout_usecs) + { + mach_timespec_t ts; + ts.tv_sec = static_cast(timeout_usecs / 1000000); + ts.tv_nsec = static_cast((timeout_usecs % 1000000) * 1000); + + // added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html + kern_return_t rc = semaphore_timedwait(m_sema, ts); + return rc == KERN_SUCCESS; + } + + void signal() + { + while (semaphore_signal(m_sema) != KERN_SUCCESS); + } + + void signal(int count) + { + while (count-- > 0) + { + while (semaphore_signal(m_sema) != KERN_SUCCESS); + } + } +}; +#elif defined(__unix__) +//--------------------------------------------------------- +// Semaphore (POSIX, Linux) +//--------------------------------------------------------- +class Semaphore +{ +private: + sem_t m_sema; + + Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + +public: + Semaphore(int initialCount = 0) + { + assert(initialCount >= 0); + int rc = sem_init(&m_sema, 0, static_cast(initialCount)); + assert(rc == 0); + (void)rc; + } + + ~Semaphore() + { + sem_destroy(&m_sema); + } + + bool wait() + { + // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error + int rc; + do { + rc = sem_wait(&m_sema); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool try_wait() + { + int rc; + do { + rc = sem_trywait(&m_sema); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool timed_wait(std::uint64_t usecs) + { + struct timespec ts; + const int usecs_in_1_sec = 1000000; + const int nsecs_in_1_sec = 1000000000; +#ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC + clock_gettime(CLOCK_MONOTONIC, &ts); +#else + clock_gettime(CLOCK_REALTIME, &ts); +#endif + ts.tv_sec += (time_t)(usecs / usecs_in_1_sec); + ts.tv_nsec += (long)(usecs % usecs_in_1_sec) * 1000; + // sem_timedwait bombs if you have more than 1e9 in tv_nsec + // so we have to clean things up before passing it in + if (ts.tv_nsec >= nsecs_in_1_sec) { + ts.tv_nsec -= nsecs_in_1_sec; + ++ts.tv_sec; + } + + int rc; + do { +#ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC + rc = sem_clockwait(&m_sema, CLOCK_MONOTONIC, &ts); +#else + rc = sem_timedwait(&m_sema, &ts); +#endif + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + void signal() + { + while (sem_post(&m_sema) == -1); + } + + void signal(int count) + { + while (count-- > 0) + { + while (sem_post(&m_sema) == -1); + } + } +}; +#else +#error Unsupported platform! (No semaphore wrapper available) +#endif + +} // end namespace details + + +//--------------------------------------------------------- +// LightweightSemaphore +//--------------------------------------------------------- +class LightweightSemaphore +{ +public: + typedef std::make_signed::type ssize_t; + +private: + std::atomic m_count; + details::Semaphore m_sema; + int m_maxSpins; + + bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) + { + ssize_t oldCount; + int spin = m_maxSpins; + while (--spin >= 0) + { + oldCount = m_count.load(std::memory_order_relaxed); + if ((oldCount > 0) && m_count.compare_exchange_strong(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed)) + return true; + std::atomic_signal_fence(std::memory_order_acquire); // Prevent the compiler from collapsing the loop. + } + oldCount = m_count.fetch_sub(1, std::memory_order_acquire); + if (oldCount > 0) + return true; + if (timeout_usecs < 0) + { + if (m_sema.wait()) + return true; + } + if (timeout_usecs > 0 && m_sema.timed_wait((std::uint64_t)timeout_usecs)) + return true; + // At this point, we've timed out waiting for the semaphore, but the + // count is still decremented indicating we may still be waiting on + // it. So we have to re-adjust the count, but only if the semaphore + // wasn't signaled enough times for us too since then. If it was, we + // need to release the semaphore too. + while (true) + { + oldCount = m_count.load(std::memory_order_acquire); + if (oldCount >= 0 && m_sema.try_wait()) + return true; + if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed)) + return false; + } + } + + ssize_t waitManyWithPartialSpinning(ssize_t max, std::int64_t timeout_usecs = -1) + { + assert(max > 0); + ssize_t oldCount; + int spin = m_maxSpins; + while (--spin >= 0) + { + oldCount = m_count.load(std::memory_order_relaxed); + if (oldCount > 0) + { + ssize_t newCount = oldCount > max ? oldCount - max : 0; + if (m_count.compare_exchange_strong(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed)) + return oldCount - newCount; + } + std::atomic_signal_fence(std::memory_order_acquire); + } + oldCount = m_count.fetch_sub(1, std::memory_order_acquire); + if (oldCount <= 0) + { + if ((timeout_usecs == 0) || (timeout_usecs < 0 && !m_sema.wait()) || (timeout_usecs > 0 && !m_sema.timed_wait((std::uint64_t)timeout_usecs))) + { + while (true) + { + oldCount = m_count.load(std::memory_order_acquire); + if (oldCount >= 0 && m_sema.try_wait()) + break; + if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed)) + return 0; + } + } + } + if (max > 1) + return 1 + tryWaitMany(max - 1); + return 1; + } + +public: + LightweightSemaphore(ssize_t initialCount = 0, int maxSpins = 10000) : m_count(initialCount), m_maxSpins(maxSpins) + { + assert(initialCount >= 0); + assert(maxSpins >= 0); + } + + bool tryWait() + { + ssize_t oldCount = m_count.load(std::memory_order_relaxed); + while (oldCount > 0) + { + if (m_count.compare_exchange_weak(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed)) + return true; + } + return false; + } + + bool wait() + { + return tryWait() || waitWithPartialSpinning(); + } + + bool wait(std::int64_t timeout_usecs) + { + return tryWait() || waitWithPartialSpinning(timeout_usecs); + } + + // Acquires between 0 and (greedily) max, inclusive + ssize_t tryWaitMany(ssize_t max) + { + assert(max >= 0); + ssize_t oldCount = m_count.load(std::memory_order_relaxed); + while (oldCount > 0) + { + ssize_t newCount = oldCount > max ? oldCount - max : 0; + if (m_count.compare_exchange_weak(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed)) + return oldCount - newCount; + } + return 0; + } + + // Acquires at least one, and (greedily) at most max + ssize_t waitMany(ssize_t max, std::int64_t timeout_usecs) + { + assert(max >= 0); + ssize_t result = tryWaitMany(max); + if (result == 0 && max > 0) + result = waitManyWithPartialSpinning(max, timeout_usecs); + return result; + } + + ssize_t waitMany(ssize_t max) + { + ssize_t result = waitMany(max, -1); + assert(result > 0); + return result; + } + + void signal(ssize_t count = 1) + { + assert(count >= 0); + ssize_t oldCount = m_count.fetch_add(count, std::memory_order_release); + ssize_t toRelease = -oldCount < count ? -oldCount : count; + if (toRelease > 0) + { + m_sema.signal((int)toRelease); + } + } + + std::size_t availableApprox() const + { + ssize_t count = m_count.load(std::memory_order_relaxed); + return count > 0 ? static_cast(count) : 0; + } +}; + +} // end namespace moodycamel